Added PKTMON support
Support for PKTMON traces in ETL and CAP files. Probably works in converted PCAPNG files as well. PKTMON reports on drops and delays with stats also in CSV file.
This commit is contained in:
Родитель
7045a71b00
Коммит
d58441d361
Двоичные данные
SQL_Network_Analyzer/.vs/SQLNetworkAnalyzer/v15/.suo
Двоичные данные
SQL_Network_Analyzer/.vs/SQLNetworkAnalyzer/v15/.suo
Двоичный файл не отображается.
|
@ -119,7 +119,54 @@ namespace SQLNA
|
|||
public string RedirectServer = "";
|
||||
public string PipeAdminName = ""; // - set in TCP Parser
|
||||
public ArrayList PipeNames = new ArrayList(); // - set in TCP Parser
|
||||
public bool hasPktmonDroppedEvent = false; // - set in ConversationData.AddFrame
|
||||
public uint pktmonDropReason = 0; // - set in ConversationData.AddFrame
|
||||
public long pktmonMaxDelay = 0; // - set in OutputText.DisplayDelayedPktmonEvents
|
||||
|
||||
public void AddFrame(FrameData f, NetworkTrace t) // replaces adding the frame directly to the frames ArrayList
|
||||
{
|
||||
//
|
||||
// Rubrick:
|
||||
//
|
||||
// If the frame is not a pktmon frame, add the the frames ArrayData and exit. This will be the norm for almost all traces analyzed.
|
||||
// If the frame is a pktmon frame, find the previous instance of this frame (go backwards for efficiency).
|
||||
// If no previous instance, create the pktmonComponentFrames ArrayList and add the frame to that and add the frame to the ArrayList and exit.
|
||||
// If there is a previous frame, add the frame to the pktmonComponentFrames ArrayList of that frame and exit.
|
||||
//
|
||||
|
||||
if (f.pktmon == null)
|
||||
{
|
||||
frames.Add(f);
|
||||
t.frames.Add(f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// search and add - use the limit of 20 frames. For a single conversation, it can't be out of sequence that much
|
||||
if (f.pktmon.eventID == 170) // drop packet event
|
||||
{
|
||||
this.hasPktmonDroppedEvent = true;
|
||||
this.pktmonDropReason = f.pktmon.DropReason;
|
||||
}
|
||||
const int BACK_COUNT_LIMIT = 20;
|
||||
int backCount = 0;
|
||||
for (int i = this.frames.Count - 1; i >= 0; i--)
|
||||
{
|
||||
FrameData priorFrame = (FrameData)frames[i];
|
||||
backCount++;
|
||||
if (priorFrame.pktmon.PktGroupId == f.pktmon.PktGroupId)
|
||||
{
|
||||
priorFrame.pktmonComponentFrames.Add(f); // found withing BACK_COUNT_LIMIT frames
|
||||
return;
|
||||
}
|
||||
if (backCount >= BACK_COUNT_LIMIT) break;
|
||||
}
|
||||
// not found within BACK_COUNT_LIMIT frames, so assume it's the first
|
||||
f.pktmonComponentFrames = new ArrayList();
|
||||
f.pktmonComponentFrames.Add(f); // make sure the frame is first in the list, so we don't have to treat it differently when performing pktmon analyses
|
||||
frames.Add(f);
|
||||
t.frames.Add(f);
|
||||
}
|
||||
}
|
||||
public bool hasLateLoginAck // added Dec 5, 2016
|
||||
{
|
||||
get
|
||||
|
|
|
@ -34,14 +34,13 @@ namespace SQLNA
|
|||
|
||||
private Guid NDIS = new Guid("2ED6006E-4729-4609-B423-3EE7BCD678EF");
|
||||
private Guid PKTMON = new Guid("4d4f80d9-c8bd-4d73-bb5b-19c90402c5ac");
|
||||
private Int16 NDIS_HEADER_LENGTH = 12;
|
||||
private readonly Int16 NDIS_HEADER_LENGTH = 12;
|
||||
private long FirstTimeStamp = 0; // should be okay to use m_sessionStartTimeQPC
|
||||
|
||||
private long m_QPCFreq;
|
||||
private uint m_eventCount;
|
||||
|
||||
private DateTime m_sessionStartTime;
|
||||
private DateTime m_sessionEndTime;
|
||||
|
||||
private TraceEventInterop.EVENT_TRACE_LOGFILEW m_logFile;
|
||||
private UInt64 m_handle;
|
||||
|
@ -96,31 +95,40 @@ namespace SQLNA
|
|||
bool f_Ethernet8023 = ((rawData->EventHeader.Keyword) & 0x1) != 0; // process Ethernet events
|
||||
bool f_Wifi = ((rawData->EventHeader.Keyword) & 0x100) != 0; // process Wi-Fi events - not yet implemented
|
||||
Guid gu = (&rawData->EventHeader)->ProviderId;
|
||||
ushort eventID = rawData->EventHeader.Id;
|
||||
Frame f = null;
|
||||
PartialFrame pf = null;
|
||||
byte[] userData = null;
|
||||
|
||||
// Debug.WriteLine($"TraceEvent_EventCallback: Frame:{m_eventCount + 1}, ProviderID: {gu}, NDIS: {NDIS}, PKTMON: {PKTMON}");
|
||||
|
||||
if (gu != NDIS && gu != PKTMON)
|
||||
{
|
||||
m_eventCount++; // assuming no fragmentation of events
|
||||
return; // process only NDIS and PKTMON events
|
||||
}
|
||||
|
||||
if (gu == PKTMON)
|
||||
{
|
||||
m_eventCount++; //
|
||||
return; // stub for right now
|
||||
}
|
||||
|
||||
if (gu == NDIS || (f_Ethernet8023 == false && f_Wifi == false)) // added Ethernet/Wi-Fi check to ignore non-parsable events
|
||||
if (gu == NDIS && f_Ethernet8023 == false && f_Wifi == false) // added Ethernet/Wi-Fi check to ignore non-parsable events
|
||||
{
|
||||
m_eventCount++; // assuming no fragmentation of non-NDIS events. could be wrong, but no way of knowing.
|
||||
return; // process only NDIS events
|
||||
}
|
||||
|
||||
if (gu == PKTMON)
|
||||
{
|
||||
if (eventID != 160 && eventID != 170)
|
||||
{
|
||||
m_eventCount++; // Track the count
|
||||
return;
|
||||
}
|
||||
// Only preocess PKTMON events that contain a network payload
|
||||
f_start = true; // these flags aren't set for PKTMON captures, but are used by the logic below, so set both to TRUE to get the effect we want
|
||||
f_end = true;
|
||||
// Debug.WriteLine($"TraceEvent_EventCallback: It's a PKTMON event.");
|
||||
}
|
||||
|
||||
if (f_start) // data complete in a single event or the initial fragment of several
|
||||
{
|
||||
|
||||
m_eventCount++; // only increment on the initial event
|
||||
|
||||
// remove partial event from the PartialFrameBuffer
|
||||
|
@ -134,20 +142,25 @@ namespace SQLNA
|
|||
// Program.logDiagnostic("Lost end of partial frame " + pf.f.frameNumber + " (PID=" + pf.ProcessID + ", TID=" + pf.ThreadID + ").");
|
||||
// Console.WriteLine("Lost end of partial frame " + pf.f.frameNumber + " (PID=" + pf.ProcessID + ", TID=" + pf.ThreadID + ").");
|
||||
}
|
||||
|
||||
short arrayOffset = gu == PKTMON ? (short)0 : NDIS_HEADER_LENGTH; // we want the pktmon header to be part of the data, not so with the NDIS header
|
||||
f = new Frame();
|
||||
f.frameNumber = m_eventCount;
|
||||
f.ticks = m_sessionStartTime.Ticks + ((long)(((rawData->EventHeader).TimeStamp - FirstTimeStamp) * 10000000 / m_QPCFreq));
|
||||
|
||||
userData = new byte[rawData->UserDataLength - NDIS_HEADER_LENGTH];
|
||||
userData = new byte[rawData->UserDataLength - arrayOffset];
|
||||
var x = ((byte*)rawData->UserData);
|
||||
for (int i = 0; i < userData.Length; i++) userData[i] = x[i + NDIS_HEADER_LENGTH];
|
||||
for (int i = 0; i < userData.Length; i++) userData[i] = x[i + arrayOffset];
|
||||
f.length = userData.Length;
|
||||
f.frameLength = (uint)userData.Length;
|
||||
f.bytesAvailable = (uint)userData.Length;
|
||||
f.data = userData;
|
||||
f.linkType = (ushort)(f_Ethernet8023 ? 1 : f_Wifi ? 6 : 0); // Ethernet -> 1, Wifi -> 6, else 0
|
||||
|
||||
if (gu == PKTMON)
|
||||
{
|
||||
f.isPKTMON = true;
|
||||
f.pktmonEventType = eventID;
|
||||
}
|
||||
|
||||
if (f_end) // add Frame to FrameBuffer directly
|
||||
{
|
||||
lock (FrameBuffer)
|
||||
|
@ -251,6 +264,7 @@ namespace SQLNA
|
|||
{
|
||||
Frame f = FrameBuffer[0];
|
||||
FrameBuffer.RemoveAt(0);
|
||||
Program.logDiagnostic($"***** Frame # {f.frameNumber}, Len: {f.frameLength}, isPktmon: {f.isPKTMON}, Event Type: {f.pktmonEventType}");
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
@ -523,18 +537,21 @@ namespace SQLNA
|
|||
};
|
||||
|
||||
// TRACEHANDLE handle type is a ULONG64 in evntrace.h. Use UInt64 here.
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")]
|
||||
[DllImport("advapi32.dll", EntryPoint = "OpenTraceW", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
|
||||
internal extern static UInt64 OpenTrace([In][Out] ref EVENT_TRACE_LOGFILEW logfile);
|
||||
internal static extern UInt64 OpenTrace([In][Out] ref EVENT_TRACE_LOGFILEW logfile);
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")]
|
||||
[DllImport("advapi32.dll"), SuppressUnmanagedCodeSecurityAttribute]
|
||||
internal extern static int ProcessTrace(
|
||||
internal static extern int ProcessTrace(
|
||||
[In] UInt64[] handleArray,
|
||||
[In] uint handleCount,
|
||||
[In] IntPtr StartTime,
|
||||
[In] IntPtr EndTime);
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")]
|
||||
[DllImport("advapi32.dll"), SuppressUnmanagedCodeSecurityAttribute]
|
||||
internal extern static int CloseTrace([In] UInt64 traceHandle);
|
||||
internal static extern int CloseTrace([In] UInt64 traceHandle);
|
||||
|
||||
// Values for ENABLE_TRACE_PARAMETERS.Version
|
||||
internal const uint ENABLE_TRACE_PARAMETERS_VERSION = 1;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace SQLNA
|
||||
{
|
||||
|
@ -12,11 +13,17 @@ namespace SQLNA
|
|||
// Helper methods for getting formatted data
|
||||
// Helper methods for dumping frames for debugging purposes
|
||||
//
|
||||
// Handling of PKTMON additional events. Should they be in-line with other frames in the conversation?
|
||||
// Probably not, since this would complicate all other analyses.
|
||||
// Should probably be in a side collection in each regular frame, so we do not bloat the conversation itself.
|
||||
//
|
||||
|
||||
public class FrameData // constructed in ParseOneFile
|
||||
public class FrameData // constructed in ParseOneFile
|
||||
{
|
||||
public ConversationData conversation = null; // set in ParseIPV4Frame and ParseIPV6Frame
|
||||
public FileData file = null; // set in ParseOneFile
|
||||
public PktmonData pktmon = null; // set in ParsePktmonFrame
|
||||
public ArrayList pktmonComponentFrames = null; // set in Conversation.AddFrame, which replaces Conversation.frames.Add; this frame will also be first in the ArrayList
|
||||
public uint frameNo = 0; // set in ParseOneFile
|
||||
public long ticks = 0; // set in ParseOneFile
|
||||
public uint frameLength = 0; // set in ParseOneFile
|
||||
|
|
|
@ -198,6 +198,14 @@ namespace SQLNA
|
|||
//
|
||||
nextOffset = (frameNumber < frameTable.Length) ? frameTable[frameNumber] : frameTableOffset; // last frame ends right before the frame table
|
||||
linkLayerBytes = (int)(nextOffset - r.BaseStream.Position);
|
||||
|
||||
//
|
||||
// Log the link types and where reading them from. NETMON files aren't 100% consistent.
|
||||
//
|
||||
// Program.logDiagnostic($"Frame={frameNumber} StartOffset=x{frameTable[frameNumber - 1].ToString("X8")} Length={nf.length} (x{nf.length.ToString("X4")}) " +
|
||||
// $"LinkPos={r.BaseStream.Position} (x{r.BaseStream.Position.ToString("X8")}) " +
|
||||
// $"NextOffset={nextOffset} (x{nextOffset.ToString("X8")})");
|
||||
|
||||
switch (linkLayerBytes)
|
||||
{
|
||||
case 0:
|
||||
|
@ -219,8 +227,17 @@ namespace SQLNA
|
|||
}
|
||||
default:
|
||||
{
|
||||
nf.linkType = networkType; // Read in the file header
|
||||
Program.logDiagnostic($"NetMonReader: Invalid link type length of {linkLayerBytes} at frame {frameNumber}. Must be 0, 1, or 2.");
|
||||
bool fReadTwoBytes = false;
|
||||
if (linkLayerBytes > 2)
|
||||
{
|
||||
fReadTwoBytes = true;
|
||||
nf.linkType = r.ReadUInt16();
|
||||
}
|
||||
else
|
||||
{
|
||||
nf.linkType = networkType; // Read in the file header
|
||||
}
|
||||
Program.logDiagnostic($"NetMonReader: Invalid link type length of {linkLayerBytes} at frame {frameNumber}. Should be 0, 1, or 2. {(fReadTwoBytes ? "Reading first 2 bytes." : "Using default link type.")}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace SQLNA
|
|||
// conversationIndex used to look up conversations. Based on XOR-ing the client port and server port to form an index
|
||||
//
|
||||
|
||||
class NetworkTrace // constructed in Main
|
||||
public class NetworkTrace // constructed in Main
|
||||
{
|
||||
|
||||
public string fileExt = null;
|
||||
|
@ -31,6 +31,8 @@ namespace SQLNA
|
|||
public ArrayList DomainControllers = new ArrayList();
|
||||
public ArrayList[] conversationIndex = new ArrayList[65536]; // short-cut to look-up conversations
|
||||
public ArrayList BadChecksumFrames = new ArrayList(); // sample of frames with bad checksum; to determine where trace was taken
|
||||
public bool hasPktmonRecords = false; // set in ParsePktmonFrame
|
||||
public bool hasPktmonDropRecords = false; // set in ParsePktmonFrame
|
||||
|
||||
public ArrayList GetConversationList(ushort index) // used for conversationIndex to speed up searching
|
||||
{
|
||||
|
|
|
@ -27,8 +27,10 @@ namespace SQLNA
|
|||
DisplayDomainControllerSummary(Trace);
|
||||
if (Program.outputConversationList) DisplaySucessfullLoginReport(Trace); // optional section; must be explicitly requested
|
||||
DisplayResetConnections(Trace);
|
||||
DisplayPktmonDrops(Trace);
|
||||
DisplayLoginErrors(Trace);
|
||||
DisplayDelayedLogins(Trace);
|
||||
DisplayDelayedPktmonEvents(Trace);
|
||||
DisplayDomainControllerLoginErrors(Trace);
|
||||
DisplayNamedPipesReport(Trace);
|
||||
DisplayAttentions(Trace);
|
||||
|
@ -76,6 +78,29 @@ namespace SQLNA
|
|||
}
|
||||
|
||||
Program.logMessage();
|
||||
|
||||
// Report on PKTMON events
|
||||
|
||||
if (Trace.hasPktmonRecords)
|
||||
{
|
||||
Program.logMessage("This trace contains repeated packets due to multiple PKTMON trace component events.");
|
||||
Program.logMessage("The total event count is reflected in the frame count above.");
|
||||
Program.logMessage("Most reports below only count the first frame in each packet group.");
|
||||
Program.logMessage();
|
||||
if (Trace.hasPktmonDropRecords)
|
||||
{
|
||||
Program.logMessage("PKTMON events show that packets were dropped in the TCP or virtual network stack.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Program.logMessage("PKTMON events do not show dropped packets in the TCP or virtual network stack.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Program.logMessage("PKTMON events were not detected.");
|
||||
}
|
||||
Program.logMessage();
|
||||
}
|
||||
|
||||
private static void DisplayTrafficStatistics(NetworkTrace Trace)
|
||||
|
@ -115,6 +140,8 @@ namespace SQLNA
|
|||
Program.logMessage(rf.GetDataText(1));
|
||||
Program.logMessage();
|
||||
|
||||
// Report on truncated packets
|
||||
|
||||
uint truncationErrors = 0;
|
||||
uint truncationLength = 0;
|
||||
|
||||
|
@ -165,12 +192,12 @@ namespace SQLNA
|
|||
}
|
||||
else
|
||||
{
|
||||
foreach(var row in GroupedRows)
|
||||
foreach (var row in GroupedRows)
|
||||
{
|
||||
Program.logMessage($"Trace was probably taken on this IP address: {row.Address}, MAC Addr {row.MAC}, ({row.AddrCount * 10}%)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Program.logMessage();
|
||||
}
|
||||
}
|
||||
|
@ -233,23 +260,23 @@ namespace SQLNA
|
|||
Program.logMessage("The following SQL Servers were visible in the network trace:\r\n");
|
||||
|
||||
ReportFormatter rf = new ReportFormatter();
|
||||
rf.SetColumnNames("IP Address:L",
|
||||
"HostName:L",
|
||||
"Port:R",
|
||||
"ServerPipe:L",
|
||||
"Version:L",
|
||||
rf.SetColumnNames("IP Address:L",
|
||||
"HostName:L",
|
||||
"Port:R",
|
||||
"ServerPipe:L",
|
||||
"Version:L",
|
||||
"Files:R",
|
||||
"Clients:R",
|
||||
"Conversations:R",
|
||||
"Clients:R",
|
||||
"Conversations:R",
|
||||
"Kerb Conv:R",
|
||||
"NTLM Conv:R",
|
||||
"MARS Conv:R",
|
||||
"non-TLS 1.2 Conv:R",
|
||||
"Redirected Conv:R",
|
||||
"Frames:R",
|
||||
"Bytes:R",
|
||||
"Resets:R",
|
||||
"Retransmits:R",
|
||||
"Frames:R",
|
||||
"Bytes:R",
|
||||
"Resets:R",
|
||||
"Retransmits:R",
|
||||
"IsClustered:R");
|
||||
|
||||
foreach (SQLServer s in Trace.sqlServers)
|
||||
|
@ -283,7 +310,7 @@ namespace SQLNA
|
|||
if (c.hasPostLoginResponse) s.hasPostLogInResponse = true;
|
||||
if (c.AttentionTime > 0) s.hasAttentions = true;
|
||||
// may see MARS enabled in PreLogin packet, or if that's missing, if the conversation has SMP packets
|
||||
if (c.isMARSEnabled || (c.smpAckCount + c.smpDataCount + c.smpSynCount + c.smpFinCount > 0)) MARSCount++;
|
||||
if (c.isMARSEnabled || (c.smpAckCount + c.smpDataCount + c.smpSynCount + c.smpFinCount > 0)) MARSCount++;
|
||||
if (c.hasLowTLSVersion)
|
||||
{
|
||||
s.hasLowTLSVersion = true;
|
||||
|
@ -627,7 +654,7 @@ namespace SQLNA
|
|||
Program.logMessage($"All {ignoredMARSConnections} reset connections were due to the MARS connection closing sequence and were benign.");
|
||||
Program.logMessage();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -639,6 +666,381 @@ namespace SQLNA
|
|||
}
|
||||
}
|
||||
|
||||
private static void DisplayPktmonDrops(NetworkTrace Trace)
|
||||
{
|
||||
if (Trace.hasPktmonRecords == false)
|
||||
{
|
||||
Program.logMessage("No Pktmon trace events were found for drop reporting.");
|
||||
Program.logMessage();
|
||||
}
|
||||
else if (Trace.hasPktmonDropRecords == false)
|
||||
{
|
||||
Program.logMessage("The trace contains Pktmon events but no drop events were found.");
|
||||
Program.logMessage();
|
||||
}
|
||||
else // pktmon with drop events
|
||||
{
|
||||
long firstTick = 0;
|
||||
long lastTick = 0;
|
||||
|
||||
if (Trace.frames != null && Trace.frames.Count > 0)
|
||||
{
|
||||
firstTick = ((FrameData)Trace.frames[0]).ticks;
|
||||
lastTick = ((FrameData)Trace.frames[Trace.frames.Count - 1]).ticks;
|
||||
}
|
||||
|
||||
foreach (SQLServer s in Trace.sqlServers)
|
||||
{
|
||||
if (s.hasPktmonDroppedEvent)
|
||||
{
|
||||
List<PktmonDropConnectionData> PktmonDropRecords = new List<PktmonDropConnectionData>();
|
||||
|
||||
// initialize graph object
|
||||
TextGraph g = new TextGraph();
|
||||
g.startTime = new DateTime(firstTick);
|
||||
g.endTime = new DateTime(lastTick);
|
||||
g.SetGraphWidth(150);
|
||||
g.fAbsoluteScale = true;
|
||||
g.SetCutoffValues(1, 3, 9, 27, 81);
|
||||
|
||||
string sqlIP = (s.isIPV6) ? utility.FormatIPV6Address(s.sqlIPHi, s.sqlIPLo) : utility.FormatIPV4Address(s.sqlIP);
|
||||
|
||||
foreach (ConversationData c in s.conversations)
|
||||
{
|
||||
if (c.hasPktmonDroppedEvent)
|
||||
{
|
||||
PktmonDropConnectionData pd = new PktmonDropConnectionData();
|
||||
|
||||
pd.clientIP = (c.isIPV6) ? utility.FormatIPV6Address(c.sourceIPHi, c.sourceIPLo) : utility.FormatIPV4Address(c.sourceIP);
|
||||
pd.sourcePort = c.sourcePort;
|
||||
pd.isIPV6 = c.isIPV6;
|
||||
pd.frames = c.frames.Count;
|
||||
pd.dropFrame = 0;
|
||||
pd.firstFile = Trace.files.IndexOf(((FrameData)(c.frames[0])).file);
|
||||
pd.lastFile = Trace.files.IndexOf(((FrameData)(c.frames[c.frames.Count - 1])).file);
|
||||
pd.startOffset = ((FrameData)c.frames[0]).ticks - firstTick;
|
||||
pd.endTicks = ((FrameData)c.frames[c.frames.Count - 1]).ticks;
|
||||
pd.endOffset = pd.endTicks - firstTick;
|
||||
pd.duration = pd.endOffset - pd.startOffset;
|
||||
|
||||
|
||||
foreach (FrameData f in c.frames) // search from beginning for first drop record
|
||||
{
|
||||
if (f.pktmonComponentFrames != null)
|
||||
{
|
||||
foreach (FrameData pktmonEvent in f.pktmonComponentFrames)
|
||||
{
|
||||
if (pktmonEvent.pktmon.eventID == 170) // this is a drop frame
|
||||
{
|
||||
pd.dropFrame = pktmonEvent.frameNo;
|
||||
pd.dropComponent = pktmonEvent.pktmon.ComponentId;
|
||||
pd.dropReason = GetPktmonDropReasonText(pktmonEvent.pktmon.DropReason) + $" ({pktmonEvent.pktmon.DropReason})";
|
||||
g.AddData(new DateTime(f.ticks), 1.0); // for graphing
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PktmonDropRecords.Add(pd);
|
||||
}
|
||||
}
|
||||
if (PktmonDropRecords.Count > 0)
|
||||
{
|
||||
Program.logMessage("The following conversations with SQL Server " + sqlIP + " on port " + s.sqlPort + " had Pktmon drop events in the TCP or virtual network stack:\r\n");
|
||||
Program.logMessage("The drop events occurred on the machine on which the trace was collected.");
|
||||
Program.logMessage();
|
||||
ReportFormatter rf = new ReportFormatter();
|
||||
switch (Program.filterFormat)
|
||||
{
|
||||
case "N":
|
||||
{
|
||||
rf.SetColumnNames("NETMON Filter (Client conv.):L", "Files:R", "Drop Frame:R", "Start Offset:R", "End Offset:R", "End Time:R", "Frames:R", "Duration:R", "Drop Component:R", "Reason:L");
|
||||
break;
|
||||
}
|
||||
case "W":
|
||||
{
|
||||
rf.SetColumnNames("WireShark Filter (Client conv.):L", "Files:R", "Drop Frame:R", "Start Offset:R", "End Offset:R", "End Time:R", "Frames:R", "Duration:R", "Drop Component:R", "Reason:L");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
rf.SetColumnNames("Client Address:L", "Port:R", "Files:R", "Drop Frame:R", "Start Offset:R", "End Offset:R", "End Time:R", "Frames:R", "Duration:R", "Drop Component:R", "Reason:L");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var OrderedRows = from row in PktmonDropRecords orderby row.endOffset ascending select row;
|
||||
|
||||
foreach (var row in OrderedRows)
|
||||
{
|
||||
switch (Program.filterFormat)
|
||||
{
|
||||
case "N": // list client IP and port as a NETMON filter string
|
||||
{
|
||||
rf.SetcolumnData((row.isIPV6 ? "IPV6" : "IPV4") + ".Address==" + row.clientIP + " AND tcp.port==" + row.sourcePort.ToString(),
|
||||
(row.firstFile == row.lastFile) ? row.firstFile.ToString() : row.firstFile + "-" + row.lastFile,
|
||||
row.dropFrame.ToString(),
|
||||
(row.startOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
|
||||
(row.endOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
|
||||
new DateTime(row.endTicks).ToString(utility.TIME_FORMAT),
|
||||
row.frames.ToString(),
|
||||
(row.duration / utility.TICKS_PER_SECOND).ToString("0.000000"),
|
||||
row.dropComponent.ToString(),
|
||||
row.dropReason);
|
||||
break;
|
||||
}
|
||||
case "W": // list client IP and port as a WireShark filter string
|
||||
{
|
||||
rf.SetcolumnData((row.isIPV6 ? "ipv6" : "ip") + ".addr==" + row.clientIP + " and tcp.port==" + row.sourcePort.ToString(),
|
||||
(row.firstFile == row.lastFile) ? row.firstFile.ToString() : row.firstFile + "-" + row.lastFile,
|
||||
row.dropFrame.ToString(),
|
||||
(row.startOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
|
||||
(row.endOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
|
||||
new DateTime(row.endTicks).ToString(utility.TIME_FORMAT),
|
||||
row.frames.ToString(),
|
||||
(row.duration / utility.TICKS_PER_SECOND).ToString("0.000000"),
|
||||
row.dropComponent.ToString(),
|
||||
row.dropReason);
|
||||
break;
|
||||
}
|
||||
default: // list client IP and port as separate columns
|
||||
{
|
||||
rf.SetcolumnData(row.clientIP,
|
||||
row.sourcePort.ToString(),
|
||||
(row.firstFile == row.lastFile) ? row.firstFile.ToString() : row.firstFile + "-" + row.lastFile,
|
||||
row.dropFrame.ToString(),
|
||||
(row.startOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
|
||||
(row.endOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
|
||||
new DateTime(row.endTicks).ToString(utility.TIME_FORMAT),
|
||||
row.frames.ToString(),
|
||||
(row.duration / utility.TICKS_PER_SECOND).ToString("0.000000"),
|
||||
row.dropComponent.ToString(),
|
||||
row.dropReason);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Program.logMessage(rf.GetHeaderText());
|
||||
Program.logMessage(rf.GetSeparatorText());
|
||||
|
||||
for (int i = 0; i < rf.GetRowCount(); i++)
|
||||
{
|
||||
Program.logMessage(rf.GetDataText(i));
|
||||
}
|
||||
|
||||
Program.logMessage();
|
||||
|
||||
//
|
||||
// Display graph
|
||||
//
|
||||
|
||||
Program.logMessage(" Distribution of PKTMON drop events.");
|
||||
Program.logMessage();
|
||||
g.ProcessData();
|
||||
Program.logMessage(" " + g.GetLine(0));
|
||||
Program.logMessage(" " + g.GetLine(1));
|
||||
Program.logMessage(" " + g.GetLine(2));
|
||||
Program.logMessage(" " + g.GetLine(3));
|
||||
Program.logMessage(" " + g.GetLine(4));
|
||||
Program.logMessage(" " + g.GetLine(5));
|
||||
|
||||
Program.logMessage();
|
||||
} // if (PktmonDropRecords.Count > 0)
|
||||
} // if (s.hasPktmonDroppedEvent)
|
||||
} // foreach (SQLServer s in Trace.sqlServers)
|
||||
} // else
|
||||
} // private static void DisplayPktmonDrops
|
||||
|
||||
private static void DisplayDelayedPktmonEvents(NetworkTrace Trace)
|
||||
{
|
||||
if (Trace.hasPktmonRecords == false)
|
||||
{
|
||||
Program.logMessage("No Pktmon trace events were found for delay reporting.");
|
||||
Program.logMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
long delayTicks = 2 * (long)utility.TICKS_PER_MILLISECOND; // any internal delay more than 2 milliseconds will be reported
|
||||
string delayWords = "2ms"; // sync with the line above
|
||||
|
||||
long firstTick = 0;
|
||||
long lastTick = 0;
|
||||
|
||||
if (Trace.frames != null && Trace.frames.Count > 0)
|
||||
{
|
||||
firstTick = ((FrameData)Trace.frames[0]).ticks;
|
||||
lastTick = ((FrameData)Trace.frames[Trace.frames.Count - 1]).ticks;
|
||||
}
|
||||
|
||||
List<PktmonDelayConnectionData> PktmonDelayRecords = new List<PktmonDelayConnectionData>();
|
||||
|
||||
// initialize graph object
|
||||
TextGraph g = new TextGraph();
|
||||
g.startTime = new DateTime(firstTick);
|
||||
g.endTime = new DateTime(lastTick);
|
||||
g.SetGraphWidth(150);
|
||||
g.fAbsoluteScale = true;
|
||||
g.SetCutoffValues(1, 3, 9, 27, 81);
|
||||
|
||||
foreach (ConversationData c in Trace.conversations)
|
||||
{
|
||||
if (c.hasPktmonDroppedEvent) continue; // this will appear in the DisplayPktmonDrops report
|
||||
|
||||
string clientIP = (c.isIPV6) ? utility.FormatIPV6Address(c.sourceIPHi, c.sourceIPLo) : utility.FormatIPV4Address(c.sourceIP);
|
||||
string serverIP = (c.isIPV6) ? utility.FormatIPV6Address(c.destIPHi, c.destIPLo) : utility.FormatIPV4Address(c.destIP);
|
||||
string protocolName = GetProtocolName(c);
|
||||
|
||||
foreach (FrameData f in c.frames)
|
||||
{
|
||||
if (f.pktmon != null && f.pktmonComponentFrames.Count > 1)
|
||||
{
|
||||
FrameData prevFrame = f;
|
||||
long diffTick = 0;
|
||||
for (int i = 1; i < f.pktmonComponentFrames.Count; i++)
|
||||
{
|
||||
FrameData nextFrame = (FrameData)f.pktmonComponentFrames[i];
|
||||
diffTick = nextFrame.ticks - prevFrame.ticks;
|
||||
if (diffTick > c.pktmonMaxDelay) c.pktmonMaxDelay = diffTick;
|
||||
if (diffTick > delayTicks)
|
||||
{
|
||||
PktmonDelayConnectionData pd = new PktmonDelayConnectionData();
|
||||
|
||||
pd.sourceIP = clientIP;
|
||||
pd.sourcePort = c.sourcePort;
|
||||
pd.destIP = serverIP;
|
||||
pd.destPort = c.destPort;
|
||||
pd.isIPV6 = c.isIPV6;
|
||||
pd.delayFrame = prevFrame.frameNo;
|
||||
pd.delayTicks = prevFrame.ticks;
|
||||
pd.delayFile = Trace.files.IndexOf(prevFrame.file);
|
||||
pd.delayOffset = prevFrame.ticks - firstTick;
|
||||
pd.delayDuration = diffTick;
|
||||
pd.delayStartComponent = prevFrame.pktmon.ComponentId;
|
||||
pd.delayEndComponent = nextFrame.pktmon.ComponentId;
|
||||
pd.protocolName = protocolName;
|
||||
|
||||
PktmonDelayRecords.Add(pd);
|
||||
g.AddData(new DateTime(prevFrame.ticks), 1.0);
|
||||
}
|
||||
prevFrame = nextFrame;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (PktmonDelayRecords.Count > 0)
|
||||
{
|
||||
Program.logMessage($"The following frames had Pktmon delays of greater than {delayWords} events in the TCP or virtual network stack:\r\n");
|
||||
Program.logMessage("The delay occurred on the machine on which the trace was collected.");
|
||||
Program.logMessage();
|
||||
ReportFormatter rf = new ReportFormatter();
|
||||
switch (Program.filterFormat)
|
||||
{
|
||||
case "N":
|
||||
{
|
||||
rf.SetColumnNames("Protocol:L", "NETMON Filter:L", "Delay Frame:R", "Delay File:R", "Delay Offset:R", "Delay Time:R", "Duration:R", "Delay Component 1:R", "Delay Component 2:R");
|
||||
break;
|
||||
}
|
||||
case "W":
|
||||
{
|
||||
rf.SetColumnNames("Protocol:L", "WireShark Filter:L", "Delay Frame:R", "Delay File:R", "Delay Offset:R", "Delay Time:R", "Duration:R", "Delay Component 1:R", "Delay Component 2:R");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
rf.SetColumnNames("Protocol:L", "Client Address:L", "Port:R", "Server Address:L", "Port:R", "Delay Frame:R", "Delay File:R", "Delay Offset:R", "Delay Time:R", "Duration:R", "Delay Component 1:R", "Delay Component 2:R");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var OrderedRows = from row in PktmonDelayRecords orderby row.delayOffset ascending select row;
|
||||
|
||||
foreach (var row in OrderedRows)
|
||||
{
|
||||
switch (Program.filterFormat)
|
||||
{
|
||||
case "N": // list client IP and port as a NETMON filter string
|
||||
{
|
||||
rf.SetcolumnData(row.protocolName,
|
||||
(row.isIPV6 ? "IPV6" : "IPV4") + ".Address==" + row.sourceIP + " AND tcp.port==" + row.sourcePort.ToString() + " AND " + (row.isIPV6 ? "IPV6" : "IPV4") + ".Address==" + row.destIP + " AND tcp.port==" + row.destPort.ToString(),
|
||||
row.delayFrame.ToString(),
|
||||
row.delayFile.ToString(),
|
||||
(row.delayOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
|
||||
new DateTime(row.delayTicks).ToString(utility.TIME_FORMAT),
|
||||
row.delayDuration.ToString(),
|
||||
row.delayStartComponent.ToString(),
|
||||
row.delayStartComponent.ToString());
|
||||
break;
|
||||
}
|
||||
case "W": // list client IP and port as a WireShark filter string
|
||||
{
|
||||
rf.SetcolumnData(row.protocolName,
|
||||
(row.isIPV6 ? "ipv6" : "ip") + ".addr==" + row.sourceIP + " and tcp.port==" + row.sourcePort.ToString() + " and " + (row.isIPV6 ? "ipv6" : "ip") + ".addr==" + row.destIP + " and tcp.port==" + row.destPort.ToString(),
|
||||
row.delayFrame.ToString(),
|
||||
row.delayFile.ToString(),
|
||||
(row.delayOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
|
||||
new DateTime(row.delayTicks).ToString(utility.TIME_FORMAT),
|
||||
row.delayDuration.ToString(),
|
||||
row.delayStartComponent.ToString(),
|
||||
row.delayStartComponent.ToString());
|
||||
break;
|
||||
}
|
||||
default: // list client IP and port as separate columns
|
||||
{
|
||||
rf.SetcolumnData(row.sourceIP,
|
||||
row.sourcePort.ToString(),
|
||||
row.destIP,
|
||||
row.destPort.ToString(),
|
||||
row.delayFrame.ToString(),
|
||||
row.delayFile.ToString(),
|
||||
(row.delayOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
|
||||
new DateTime(row.delayTicks).ToString(utility.TIME_FORMAT),
|
||||
row.delayDuration.ToString(),
|
||||
row.delayStartComponent.ToString(),
|
||||
row.delayStartComponent.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // foreach (var row in OrderedRows)
|
||||
|
||||
Program.logMessage(rf.GetHeaderText());
|
||||
Program.logMessage(rf.GetSeparatorText());
|
||||
|
||||
for (int i = 0; i < rf.GetRowCount(); i++)
|
||||
{
|
||||
Program.logMessage(rf.GetDataText(i));
|
||||
}
|
||||
|
||||
Program.logMessage();
|
||||
|
||||
//
|
||||
// Display graph
|
||||
//
|
||||
|
||||
Program.logMessage(" Distribution of PKTMON delay events.");
|
||||
Program.logMessage();
|
||||
g.ProcessData();
|
||||
Program.logMessage(" " + g.GetLine(0));
|
||||
Program.logMessage(" " + g.GetLine(1));
|
||||
Program.logMessage(" " + g.GetLine(2));
|
||||
Program.logMessage(" " + g.GetLine(3));
|
||||
Program.logMessage(" " + g.GetLine(4));
|
||||
Program.logMessage(" " + g.GetLine(5));
|
||||
|
||||
Program.logMessage();
|
||||
} // if (PktmonDropRecords.Count > 0)
|
||||
else
|
||||
{
|
||||
Program.logMessage($"Pktmon events were found in the trace, but no events took more than {delayWords} to complete.");
|
||||
Program.logMessage();
|
||||
}
|
||||
|
||||
|
||||
} // else
|
||||
} // DisplayDelayedPktmonEvents(NetworkTrace Trace)
|
||||
|
||||
private static void DisplayDelayedLogins(NetworkTrace Trace)
|
||||
{
|
||||
bool hasDelay = false;
|
||||
|
@ -2484,7 +2886,7 @@ namespace SQLNA
|
|||
|
||||
private static void OutputStats(NetworkTrace Trace)
|
||||
{
|
||||
Program.logStat(@"SourceIP,SourcePort,DestIP,DestPort,IPVersion,Protocol,Syn,Fin,Reset,Retransmit,ClientDup,ServerDup,KeepAlive,Integrated Login,NTLM,Login7,Encrypted,Mars,MaxPayloadSize,PayloadSizeLimit,Frames,Bytes,SentBytes,ReceivedBytes,Bytes/Sec,StartFile,EndFile,StartTime,EndTime,Duration,ServerName,ServerVersion,DatabaseName,ServerTDSVersion,ClientTDSVersion,ServerTLSVersion,ClientTLSVersion,RedirSrv,RedirPort,Error,ErrorState,ErrorMessage,");
|
||||
Program.logStat(@"SourceIP,SourcePort,DestIP,DestPort,IPVersion,Protocol,Syn,Fin,Reset,Retransmit,ClientDup,ServerDup,KeepAlive,Integrated Login,NTLM,Login7,Encrypted,Mars,Pktmon,MaxPktmonDelay,PktmonDrop,PktmonDropReason,MaxPayloadSize,PayloadSizeLimit,Frames,Bytes,SentBytes,ReceivedBytes,Bytes/Sec,StartFile,EndFile,StartTime,EndTime,Duration,ServerName,ServerVersion,DatabaseName,ServerTDSVersion,ClientTDSVersion,ServerTLSVersion,ClientTLSVersion,RedirSrv,RedirPort,Error,ErrorState,ErrorMessage,");
|
||||
foreach (ConversationData c in Trace.conversations)
|
||||
{
|
||||
int firstFile = Trace.files.IndexOf(((FrameData)(c.frames[0])).file);
|
||||
|
@ -2520,6 +2922,11 @@ namespace SQLNA
|
|||
(c.hasLogin7 ? "Y" : "") + "," +
|
||||
(c.isEncrypted ? "Y" : "") + "," +
|
||||
(c.isSQL && (c.isMARSEnabled || (c.smpAckCount + c.smpSynCount + c.smpFinCount + c.smpDataCount) > 0) ? "Y" : "") + "," +
|
||||
// Pktmon,MaxPktmonDelay,PktmonDrop,PktmonDropReason
|
||||
(Trace.hasPktmonRecords ? "Y" : "") + "," +
|
||||
(Trace.hasPktmonRecords ? $"{(c.pktmonMaxDelay / utility.TICKS_PER_SECOND).ToString("0.000000")}" : "") + "," +
|
||||
(Trace.hasPktmonRecords && c.hasPktmonDroppedEvent ? $"Y" : "") + "," +
|
||||
(Trace.hasPktmonRecords && c.hasPktmonDroppedEvent ? GetPktmonDropReasonText(c.pktmonDropReason) : "") + "," +
|
||||
c.maxPayloadSize + "," +
|
||||
(c.maxPayloadLimit ? "Y": "") + "," +
|
||||
c.frames.Count + "," +
|
||||
|
@ -2560,5 +2967,60 @@ namespace SQLNA
|
|||
return "TCP";
|
||||
}
|
||||
|
||||
private static string GetPktmonDropReasonText(uint value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case 0: return "Unspecified";
|
||||
case 1: return "Invalid Data";
|
||||
case 2: return "Invalid Packet";
|
||||
case 3: return "Insufficient resources";
|
||||
case 4: return "Adapter not ready";
|
||||
case 5: return "Media Disconnected";
|
||||
case 6: return "Not accepted";
|
||||
case 7: return "Device busy";
|
||||
case 8: return "Filtered";
|
||||
case 9: return "Filtered VLAN";
|
||||
case 10: return "Unauthorized VLAN";
|
||||
case 11: return "Unauthorized MAC";
|
||||
case 12: return "Failed security policy";
|
||||
case 13: return "Failed pVlan setting";
|
||||
case 14: return "QoS drop";
|
||||
case 15: return "IPSec drop";
|
||||
case 16: return "Spoofed MAC address is not allowed";
|
||||
case 17: return "Failed DHCP guard";
|
||||
case 18: return "Failed Router Guard";
|
||||
case 19: return "Bridge is not allowed inside VM";
|
||||
case 20: return "Virtual Subnet ID does not match";
|
||||
case 21: return "Required vSwitch extension is missing";
|
||||
case 22: return "Creating vSwitch over another vSwitch is not allowed";
|
||||
case 23: return "MTU mismatch";
|
||||
case 24: return "Native forwarding required";
|
||||
case 25: return "Invalid VLAN format";
|
||||
case 26: return "Invalid destination MAC";
|
||||
case 27: return "Invalid source MAC";
|
||||
case 28: return "First NB too small";
|
||||
case 29: return "Windows Network Virtualization error";
|
||||
case 30: return "Storm limit exceeded";
|
||||
case 31: return "ICMP request injected by switch";
|
||||
case 32: return "Failed to update destination list";
|
||||
case 33: return "Destination NIC is disabled";
|
||||
case 34: return "Packet does not match destination NIC packet filter";
|
||||
case 35: return "vSwitch data flow is disabled";
|
||||
case 36: return "Port isolation setting does not allow untagged traffic";
|
||||
case 37: return "Invalid PD queue";
|
||||
case 38: return "Adapter is in low power state";
|
||||
case 101: return "Adapter paused";
|
||||
case 102: return "Adapter reset in progress";
|
||||
case 103: return "Send aborted";
|
||||
case 104: return "Unsupported EtherType";
|
||||
case 201: return "Microport error";
|
||||
case 202: return "VF not ready";
|
||||
case 203: return "Microport not ready";
|
||||
case 204: return "VMBus error";
|
||||
default: return $"Unknown value: {value}";
|
||||
}
|
||||
} // end of GetPktMonDropReason
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -187,7 +187,7 @@ namespace SQLNA
|
|||
BinaryReader r = null;
|
||||
ReaderBase rb = null;
|
||||
ETLFileReader er = null;
|
||||
bool isETL = filePath.ToLower().EndsWith(".etl"); // ETL files have no maging number. Must be done by file name.
|
||||
bool isETL = filePath.ToLower().EndsWith(".etl"); // ETL files have no magic number. Must be done by file name.
|
||||
Frame frame = null;
|
||||
|
||||
bool f_ReportedOther = false;
|
||||
|
@ -254,69 +254,76 @@ namespace SQLNA
|
|||
if (frame.ticks > file.endTick) file.endTick = frame.ticks;
|
||||
file.frameCount++;
|
||||
|
||||
switch (frame.linkType)
|
||||
if (frame.isPKTMON)
|
||||
{
|
||||
case 0: // unknown - default to ethernet
|
||||
case 1: // Ethernet
|
||||
{
|
||||
ParseEthernetFrame(frame.data, 0, t, f);
|
||||
break;
|
||||
}
|
||||
case 6: // WiFi
|
||||
{
|
||||
ParseWifiFrame(frame.data, 0, t, f); // TODO flesh this out
|
||||
// Test file: \Documents\Interesting Network Traces\WifiTrace\
|
||||
break;
|
||||
}
|
||||
case 101: // Raw - first byte of payload - high nybble = 0x4n -> IPV4; high nybble = 0x6n -> IPV6
|
||||
{
|
||||
byte payloadType = (byte)(frame.data[0] & 0xF0);
|
||||
switch (payloadType)
|
||||
ParsePktmonFrame(frame.data, 0, t, f, frame.pktmonEventType);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (frame.linkType)
|
||||
{
|
||||
case 0: // unknown - default to ethernet
|
||||
case 1: // Ethernet
|
||||
{
|
||||
case 0x40:
|
||||
{
|
||||
ParseIPV4Frame(frame.data, 0, t, f);
|
||||
break;
|
||||
}
|
||||
case 0x60:
|
||||
{
|
||||
ParseIPV6Frame(frame.data, 0, t, f);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!f_ReportedOther)
|
||||
ParseEthernetFrame(frame.data, 0, t, f);
|
||||
break;
|
||||
}
|
||||
case 6: // WiFi
|
||||
{
|
||||
ParseWifiFrame(frame.data, 0, t, f); // TODO flesh this out
|
||||
// Test file: \Documents\Interesting Network Traces\WifiTrace\
|
||||
break;
|
||||
}
|
||||
case 101: // Raw - first byte of payload - high nybble = 0x4n -> IPV4; high nybble = 0x6n -> IPV6
|
||||
{
|
||||
byte payloadType = (byte)(frame.data[0] & 0xF0);
|
||||
switch (payloadType)
|
||||
{
|
||||
case 0x40:
|
||||
{
|
||||
Program.logDiagnostic($"Frame {frame.frameNumber}: Raw Protocol {frame.linkType} (0x{frame.linkType.ToString("X4")}). Payload header type {frame.data[0]} is not IPV4 or IPV6.");
|
||||
f_ReportedOther = true;
|
||||
ParseIPV4Frame(frame.data, 0, t, f);
|
||||
break;
|
||||
}
|
||||
case 0x60:
|
||||
{
|
||||
ParseIPV6Frame(frame.data, 0, t, f);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!f_ReportedOther)
|
||||
{
|
||||
Program.logDiagnostic($"Frame {frame.frameNumber}: Raw Protocol {frame.linkType} (0x{frame.linkType.ToString("X4")}). Payload header type {frame.data[0]} is not IPV4 or IPV6.");
|
||||
f_ReportedOther = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x0071: // Linux Cooked Capture - no MAC addresses, just IP and higher protocols
|
||||
case 0xE071: // Linux Cooked Capture - no MAC addresses, just IP and higher protocols
|
||||
{
|
||||
ParseLinuxCookedFrame(frame.data, 0, t, f);
|
||||
break;
|
||||
}
|
||||
case 0xFFE0: // NetEvent (usually in ETL and parsed by now) - happens when NETMON saves ETL capture as a CAP file
|
||||
{
|
||||
ParseNetEventFrame(frame.data, 0, t, f); // TODO flesh this out
|
||||
// Test file: \Documents\Interesting Network Traces\Filtered ETL in a CAP File - fix SQLNA\*_filtered.cap
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!f_ReportedOther)
|
||||
case 0x0071: // Linux Cooked Capture - no MAC addresses, just IP and higher protocols
|
||||
case 0xE071: // Linux Cooked Capture - no MAC addresses, just IP and higher protocols
|
||||
{
|
||||
Program.logDiagnostic($"Frame {frame.frameNumber}: Unknown Protocol {frame.linkType} (0x{frame.linkType.ToString("X4")}). Packet ignored.");
|
||||
f_ReportedOther = true;
|
||||
ParseLinuxCookedFrame(frame.data, 0, t, f);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xFFE0: // NetEvent (usually in ETL and parsed by now) - happens when NETMON saves ETL capture as a CAP file
|
||||
{
|
||||
ParseNetEventFrame(frame.data, 0, t, f); // TODO flesh this out
|
||||
// Test file: \Documents\Interesting Network Traces\Filtered ETL in a CAP File - fix SQLNA\*_filtered.cap
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!f_ReportedOther)
|
||||
{
|
||||
Program.logDiagnostic($"Frame {frame.frameNumber}: Unknown Protocol {frame.linkType} (0x{frame.linkType.ToString("X4")}). Packet ignored.");
|
||||
f_ReportedOther = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -479,6 +486,7 @@ namespace SQLNA
|
|||
public static void ParseNetEventFrame(byte[] b, int offset, NetworkTrace t, FrameData f)
|
||||
{
|
||||
Guid NDIS = new Guid("2ED6006E-4729-4609-B423-3EE7BCD678EF");
|
||||
Guid PKTMON = new Guid("4d4f80d9-c8bd-4d73-bb5b-19c90402c5ac");
|
||||
ushort eventID = 0;
|
||||
ushort flags = 0;
|
||||
long fileTicks = 0;
|
||||
|
@ -486,8 +494,9 @@ namespace SQLNA
|
|||
Boolean isEthernet = false;
|
||||
Boolean isWifi = false;
|
||||
Boolean isFragment = false;
|
||||
Boolean isPktmon = false;
|
||||
ushort userDataLength = 0;
|
||||
uint NDISFragmentSize = 0;
|
||||
uint ETLFragmentSize = 0;
|
||||
|
||||
// Read NetEvent Header
|
||||
offset = 4; // bypass size and header type, 2 bytes each; we get size later
|
||||
|
@ -502,12 +511,13 @@ namespace SQLNA
|
|||
byte[] GuidBytes = new byte[16];
|
||||
Array.Copy(b, offset, GuidBytes, 0, 16);
|
||||
Guid ProviderID = new Guid(GuidBytes); // 0x6E00D62E29470946B4233EE7BCD678EF yields GUID {2ed6006e-4729-4609-b423-3ee7bcd678ef}
|
||||
if (!ProviderID.Equals(NDIS)) return; // not the provider we want
|
||||
isPktmon = ProviderID.Equals(PKTMON);
|
||||
if (!ProviderID.Equals(NDIS) && !isPktmon) return; // not the provider we want
|
||||
offset += 16;
|
||||
|
||||
// Read Descriptor - Event ID
|
||||
eventID = utility.ReadUInt16(b, offset);
|
||||
if (eventID != 1001) return; // not the event we want
|
||||
if (isPktmon == false && eventID != 1001) return; // not the NDIS event we want
|
||||
offset += 2;
|
||||
|
||||
offset += 6; // skip Version (1), Channel (1), Level (1), OpCode (1), Task (2)
|
||||
|
@ -515,7 +525,7 @@ namespace SQLNA
|
|||
// Read Descriptor KeyWord Bytes (8 bytes total)
|
||||
isEthernet = (b[offset] & 0x01) != 0; // isEthernet and isWifi are mutually exclusive
|
||||
isWifi = (b[offset + 1] & 0x80) != 0;
|
||||
if (isEthernet == false && isWifi == false) return; // not a link layer we support
|
||||
if (isPktmon == false && isEthernet == false && isWifi == false) return; // not a link layer we support
|
||||
|
||||
isFragment = (b[offset + 3] & 0xC0) != 0xC0;
|
||||
if (isFragment) // we aren't supporting fragments right now, log it
|
||||
|
@ -535,16 +545,20 @@ namespace SQLNA
|
|||
// Read NDIS Header (12 bytes for eventID 1001)
|
||||
|
||||
offset += 8; // skip MiniportIfIndex (4), LowerIfIndex (4)
|
||||
NDISFragmentSize = utility.ReadUInt32(b, offset);
|
||||
if (NDISFragmentSize + 12 != userDataLength)
|
||||
ETLFragmentSize = utility.ReadUInt32(b, offset);
|
||||
if (ETLFragmentSize + 12 != userDataLength)
|
||||
{
|
||||
Program.logDiagnostic("ParseNetEventFrame. Frame " + f.frameNo + ". userDataLength - NDISFragmentSize != 12 . Ignoring.");
|
||||
Program.logDiagnostic("ParseNetEventFrame. Frame " + f.frameNo + ". userDataLength - ETLFragmentSize != 12 . Ignoring.");
|
||||
return;
|
||||
}
|
||||
offset += 4;
|
||||
|
||||
// one of these is guaranteed to be called; the case where both are false is tested above
|
||||
if (isEthernet)
|
||||
if (isPktmon)
|
||||
{
|
||||
ParsePktmonFrame(b, offset, t, f, eventID);
|
||||
}
|
||||
else if (isEthernet)
|
||||
{
|
||||
ParseEthernetFrame(b, offset, t, f);
|
||||
}
|
||||
|
@ -554,6 +568,68 @@ namespace SQLNA
|
|||
}
|
||||
}
|
||||
|
||||
public static void ParsePktmonFrame(byte[] b, int offset, NetworkTrace t, FrameData f, ushort eventID)
|
||||
{
|
||||
switch (eventID)
|
||||
{
|
||||
case 10: // PktMon_DriverEntryFailed DriverEntryFailed;
|
||||
case 20: // PktMon_ComponentInfo ComponentInfo;
|
||||
case 30: // PktMon_CompPropUint CompPropUint;
|
||||
case 40: // PktMon_CompPropGuid CompPropGuid;
|
||||
case 50: // PktMon_CompPropHex32 CompPropHex32;
|
||||
case 60: // PktMon_CompPropNdisMedium CompPropNdisMedium;
|
||||
case 70: // PktMon_CompPropBinary CompPropBinary;
|
||||
case 73: // PktMon_CompPropString CompPropString;
|
||||
case 75: // PktMon_CompPropEtherType CompPropEtherType;
|
||||
case 80: // PktMon_PacketDropCounters PacketDropCounters;
|
||||
case 90: // PktMon_PacketFlowCounters PacketFlowCounters;
|
||||
case 100: // PktMon_PktFilterIPv4 PktFilterIPv4;
|
||||
case 110: // PktMon_PktFilterIPv6 PktFilterIPv6;
|
||||
case 120: // PktMon_NblParsedIpv4 NblParsedIpv4;
|
||||
case 130: // PktMon_NblParsedIpv6 NblParsedIpv6;
|
||||
case 140: // PktMon_NblParsedDropIpv4 NblParsedDropIpv4;
|
||||
case 150: // PktMon_NblParsedDropIpv6 NblParsedDropIpv6;
|
||||
case 180: // PktMon_PktNblInfo PktNblInfo;
|
||||
case 190: // PktMon_PktDropNblInfo PktDropNblInfo;
|
||||
// ignore the frame
|
||||
break;
|
||||
case 160: // PktMon_FramePayload FramePayload;
|
||||
case 170: // PktMon_FrameDropPayload FrameDropPayload;
|
||||
// check the properties and parse the payload as Ethernet
|
||||
// both event types have the same data structure and is reflected in the PktmonData class
|
||||
PktmonData pkm = new PktmonData();
|
||||
pkm.eventID = eventID; // 160 for the normal frame data event or 170 for the frame drop event
|
||||
pkm.PktGroupId = utility.ReadUInt64(b, offset); offset += 8;
|
||||
pkm.PktNumber = utility.ReadUInt16(b, offset); offset += 2;
|
||||
pkm.AppearanceCount = utility.ReadUInt16(b, offset); offset += 2;
|
||||
pkm.DirTag = utility.ReadUInt16(b, offset); offset += 2;
|
||||
pkm.PacketType = utility.ReadUInt16(b, offset); offset += 2;
|
||||
pkm.ComponentId = utility.ReadUInt16(b, offset); offset += 2;
|
||||
pkm.EdgeId = utility.ReadUInt16(b, offset); offset += 2;
|
||||
pkm.FilterId = utility.ReadUInt16(b, offset); offset += 2;
|
||||
pkm.DropReason = utility.ReadUInt32(b, offset); offset += 4;
|
||||
pkm.DropLocation = utility.ReadUInt32(b, offset); offset += 4;
|
||||
pkm.OriginalPayloadSize = utility.ReadUInt16(b, offset); offset += 2;
|
||||
pkm.LoggedPayloadSize = utility.ReadUInt16(b, offset); offset += 2;
|
||||
|
||||
//
|
||||
// The ETLFileReader files make f.frameLength and f.capturedFrameLength the same even if the packet was truncated.
|
||||
// However, the PKTMON record logs the discrepancy.
|
||||
// Adjust f.capturedFrameLength down by the difference.
|
||||
//
|
||||
if (pkm.LoggedPayloadSize != pkm.OriginalPayloadSize && f.capturedFrameLength == f.frameLength) f.capturedFrameLength -= (ushort)(pkm.OriginalPayloadSize - pkm.LoggedPayloadSize);
|
||||
|
||||
f.pktmon = pkm;
|
||||
t.hasPktmonRecords = true;
|
||||
if (pkm.eventID == 170) t.hasPktmonDropRecords = true; // if it's a dropped packet event type
|
||||
ParseEthernetFrame(b, offset, t, f);
|
||||
break;
|
||||
default:
|
||||
// ignore the frame
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ParseGenericRoutingEncapsulation(byte[] b, int offset, NetworkTrace t, FrameData f)
|
||||
{
|
||||
byte flags1 = b[offset];
|
||||
|
@ -944,7 +1020,7 @@ namespace SQLNA
|
|||
ConversationData c = t.GetIPV4Conversation(sourceIP, SPort, destIP, DPort); // adds conversation if new
|
||||
|
||||
//
|
||||
// Purpose: Do not record duplicate frames
|
||||
// Purpose: Do not record duplicate frames unless it has a PktmonData record associated with it
|
||||
//
|
||||
// Why: This is an artifact of taking the trace and is not the same as retransmitted frames,
|
||||
// which have the same TCP sequence # but different PacketID values
|
||||
|
@ -961,19 +1037,22 @@ namespace SQLNA
|
|||
//
|
||||
|
||||
int backCount = 0;
|
||||
|
||||
for (int j = c.frames.Count - 1; j >= 0; j--) // look in descending order for the same Packet ID number
|
||||
|
||||
if (f.pktmon == null) // we want to see the pktmon trace points
|
||||
{
|
||||
FrameData priorFrame = (FrameData)c.frames[j];
|
||||
if (f.isFromClient == priorFrame.isFromClient) // only consider packets going in the same direction as the current packet
|
||||
for (int j = c.frames.Count - 1; j >= 0; j--) // look in descending order for the same Packet ID number
|
||||
{
|
||||
backCount++;
|
||||
if (f.packetID == priorFrame.packetID)
|
||||
FrameData priorFrame = (FrameData)c.frames[j];
|
||||
if (f.isFromClient == priorFrame.isFromClient) // only consider packets going in the same direction as the current packet
|
||||
{
|
||||
if (f.isFromClient) c.duplicateClientPackets++; else c.duplicateServerPackets++;
|
||||
return; // do not record this duplicate frame
|
||||
backCount++;
|
||||
if (f.packetID == priorFrame.packetID)
|
||||
{
|
||||
if (f.isFromClient) c.duplicateClientPackets++; else c.duplicateServerPackets++;
|
||||
return; // do not record this duplicate frame
|
||||
}
|
||||
if (backCount >= BACK_COUNT_LIMIT) break; // none found in last 20 frames from the same side of the conversation
|
||||
}
|
||||
if (backCount >= BACK_COUNT_LIMIT) break; // none found in last 20 frames from the same side of the conversation
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1020,8 +1099,7 @@ namespace SQLNA
|
|||
c.truncatedFrameLength = f.capturedFrameLength;
|
||||
}
|
||||
f.conversation = c;
|
||||
t.frames.Add(f);
|
||||
c.frames.Add(f);
|
||||
c.AddFrame(f, t); // optionally add to the NetworkTrace frames collection, too
|
||||
|
||||
// Is the Frame from Client or Server? This may be reversed later in ReverseBackwardConversations.
|
||||
if (sourceIP == c.sourceIP) f.isFromClient = true;
|
||||
|
@ -1124,8 +1202,7 @@ namespace SQLNA
|
|||
c.truncatedFrameLength = f.capturedFrameLength;
|
||||
}
|
||||
f.conversation = c;
|
||||
t.frames.Add(f);
|
||||
c.frames.Add(f);
|
||||
c.AddFrame(f, t);
|
||||
|
||||
//Is the Frame from Client or Server?
|
||||
if (sourceIPHi == c.sourceIPHi && sourceIPLo == c.sourceIPLo)
|
||||
|
@ -1343,14 +1420,12 @@ namespace SQLNA
|
|||
foreach (ConversationData c in t.conversations)
|
||||
{
|
||||
FrameData f = (FrameData)c.frames[0]; // get first frame
|
||||
//
|
||||
// tests are done this way because the E flag may be set occasionally and must not let that interfere with the comparison
|
||||
//
|
||||
if (((f.flags & (byte)(TCPFlag.SYN)) != 0) && ((f.flags & (byte)(TCPFlag.ACK)) == 0) && !f.isFromClient)
|
||||
|
||||
if (f.hasSYNFlag && !f.hasACKFlag && !f.isFromClient)
|
||||
{
|
||||
TDSParser.reverseSourceDest(c); // if the first packet is SYN and from the server - reverse the conversation
|
||||
}
|
||||
else if (((f.flags & (byte)(TCPFlag.SYN)) != 0) && ((f.flags & (byte)(TCPFlag.ACK)) != 0) && f.isFromClient)
|
||||
else if (f.hasSYNFlag && f.hasACKFlag && f.isFromClient)
|
||||
{
|
||||
TDSParser.reverseSourceDest(c); // if the first packet is ACK+SYN and from client - reverse the conversation
|
||||
}
|
||||
|
@ -1368,6 +1443,7 @@ namespace SQLNA
|
|||
for (int i = 0; i < c.frames.Count; i++) // process the frames in the current conversation in ascending order
|
||||
{
|
||||
FrameData f = (FrameData)c.frames[i];
|
||||
|
||||
int backCount = 0;
|
||||
payloadLen = f.payloadLength;
|
||||
if (payloadLen < 8) continue; // skip non-TDS packets, especially keep-alive ACKs that have a payload length of 1 - may skip a retransmit of a continuation packet with a small residual payload
|
||||
|
@ -1375,9 +1451,11 @@ namespace SQLNA
|
|||
for (int j = i - 1; j >= 0; j--) // look in descending order for the same sequence number and payload length
|
||||
{
|
||||
FrameData priorFrame = (FrameData)c.frames[j];
|
||||
|
||||
if (f.isFromClient == priorFrame.isFromClient)
|
||||
{
|
||||
backCount++;
|
||||
if (f.conversation.isIPV6 == false && f.packetID == priorFrame.packetID) continue; // if packet is IPV4 and Packet IDs match, that's not a retransmitted packet
|
||||
priorPayloadLen = priorFrame.payloadLength;
|
||||
if ((payloadLen == priorPayloadLen) &&
|
||||
((f.seqNo == priorFrame.seqNo) || ((f.seqNo > priorFrame.seqNo) && (f.seqNo < (priorFrame.seqNo + priorPayloadLen)))))
|
||||
|
@ -1406,11 +1484,13 @@ namespace SQLNA
|
|||
{
|
||||
FrameData f = (FrameData)c.frames[i];
|
||||
if (!f.isKeepAlive) continue; // skip non-Keep-Alive packets; payload = 1 and flags = ACK and no others
|
||||
|
||||
int backCount = 0;
|
||||
|
||||
for (int j = i - 1; j >= 0; j--) // look in descending order for the same sequence number and payload length
|
||||
{
|
||||
FrameData priorFrame = (FrameData)c.frames[j];
|
||||
|
||||
if (f.isFromClient == priorFrame.isFromClient)
|
||||
{
|
||||
backCount++;
|
||||
|
@ -1440,12 +1520,14 @@ namespace SQLNA
|
|||
for (int i = 0; i < c.frames.Count; i++) // process the frames in the current conversation in ascending order
|
||||
{
|
||||
FrameData f = (FrameData)c.frames[i];
|
||||
|
||||
int backCount = 0;
|
||||
if (f.payloadLength == 0) continue; // not checking ACK packets
|
||||
|
||||
for (int j = i - 1; j >= 0; j--) // look in descending order for the same ack number - if push flag set, abort
|
||||
{
|
||||
FrameData priorFrame = (FrameData)c.frames[j];
|
||||
|
||||
if ((f.isFromClient == priorFrame.isFromClient)) // continuation frames have no SMP header, so no need to match on that field
|
||||
{
|
||||
backCount++;
|
||||
|
@ -1610,6 +1692,5 @@ namespace SQLNA
|
|||
return pipeInfo;
|
||||
}
|
||||
} // end of GetPipeName
|
||||
|
||||
} // end of class
|
||||
} // end of namespace
|
||||
|
|
|
@ -31,9 +31,8 @@ namespace SQLNA
|
|||
public static string filterFormat = ""; // blank | A | N | W if N or W, replace the Client IP and Port in reports with a filter string in either NETMON or WireShark format
|
||||
// filterFormat A = AUTO, will perform NETMON or WirreShark filters based on the capture type ... ETL -> Netmon format
|
||||
|
||||
//public const string VERSION_NUMBER = "1.5.1760.0";
|
||||
public static string VERSION_NUMBER = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
public const string UPDATE_DATE = "2021/12/08";
|
||||
public const string UPDATE_DATE = "2022/04/01";
|
||||
public const string GITHUB_PROJECT_URL = "https://github.com/microsoft/CSS_SQL_Networking_Tools";
|
||||
|
||||
static void Main(string[] args)
|
||||
|
@ -229,28 +228,6 @@ namespace SQLNA
|
|||
OutputText.TextReport(Trace);
|
||||
statFile.Close();
|
||||
statFile = null;
|
||||
|
||||
// dump conversations to console window
|
||||
//if (dumpConversations)
|
||||
//{
|
||||
// foreach (ConversationData c in Trace.conversations)
|
||||
// {
|
||||
// if (c.sourcePort != 62729) continue;
|
||||
// Console.WriteLine();
|
||||
// Console.WriteLine(c.ColumnHeader1());
|
||||
// Console.WriteLine(c.ColumnHeader2());
|
||||
// Console.WriteLine(c.ColumnData());
|
||||
// Console.WriteLine();
|
||||
// Console.WriteLine(((FrameData)(c.frames[0])).ColumnHeader1());
|
||||
// Console.WriteLine(((FrameData)(c.frames[0])).ColumnHeader2());
|
||||
|
||||
// foreach (FrameData f in c.frames)
|
||||
// {
|
||||
// Console.WriteLine(f.ColumnData());
|
||||
// }
|
||||
// }
|
||||
// Console.ReadLine();
|
||||
//}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
|||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.5.1888.0")]
|
||||
[assembly: AssemblyFileVersion("1.5.1888.0")]
|
||||
[assembly: AssemblyVersion("1.5.1927.0")]
|
||||
[assembly: AssemblyFileVersion("1.5.1927.0")]
|
||||
|
|
|
@ -15,13 +15,15 @@ namespace SQLNA
|
|||
|
||||
public class Frame
|
||||
{
|
||||
public UInt32 frameNumber; // Frame number.
|
||||
public UInt32 frameLength; // actual length of packet
|
||||
public UInt32 bytesAvailable; // number of bytes available in packet.
|
||||
public long ticks; // Absolute ticks of frame (calculated)
|
||||
public byte[] data; // Byte data for frame.
|
||||
public long length = 0; // Length of data in bytes.
|
||||
public ushort linkType = 0; // what provider is it - Ethernet (1), Wifi, etc.
|
||||
public UInt32 frameNumber; // Frame number.
|
||||
public UInt32 frameLength; // actual length of packet
|
||||
public UInt32 bytesAvailable; // number of bytes available in packet.
|
||||
public long ticks; // Absolute ticks of frame (calculated)
|
||||
public byte[] data; // Byte data for frame.
|
||||
public long length = 0; // Length of data in bytes.
|
||||
public bool isPKTMON = false; // ETLFileReader sets this - if false, use the linkType to determine the parser
|
||||
public ushort pktmonEventType = 0; // ETLFileReader sets this
|
||||
public ushort linkType = 0; // what provider is it - Ethernet (0 or 1), Wifi, etc.
|
||||
};
|
||||
|
||||
public abstract class ReaderBase
|
||||
|
|
|
@ -48,6 +48,8 @@
|
|||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<RunCodeAnalysis>true</RunCodeAnalysis>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<NoWarn>
|
||||
</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace SQLNA
|
|||
// Helper function to find an existing conversation to be added to
|
||||
//
|
||||
|
||||
class SSRPData
|
||||
public class SSRPData
|
||||
{
|
||||
public uint sqlIP = 0;
|
||||
public ulong sqlIPHi = 0;
|
||||
|
|
|
@ -92,14 +92,39 @@ namespace SQLNA
|
|||
public bool hasLowTLSVersion = false; // set in OutputText.DisplaySQLServerSummary
|
||||
public bool hasPostLogInResponse = false;
|
||||
public bool hasRedirectedConnections = false;
|
||||
public bool hasPktmonDroppedEvent = false; //
|
||||
public string serverVersion = "";
|
||||
public string instanceName = "";
|
||||
public string sqlHostName = "";
|
||||
public string namedPipe = "";
|
||||
public string isClustered = "";
|
||||
public ArrayList conversations = new ArrayList(1024); // pre-size to moderate starting amount - SQL may have few or many conversations
|
||||
|
||||
public void AddConversation(ConversationData c)
|
||||
{
|
||||
conversations.Add(c);
|
||||
if (serverVersion == "" && c.serverVersion != null) serverVersion = c.serverVersion;
|
||||
if (sqlHostName == "" && c.serverName != null) sqlHostName = c.serverName;
|
||||
if (hasPktmonDroppedEvent == false && c.hasPktmonDroppedEvent == true) hasPktmonDroppedEvent = true;
|
||||
}
|
||||
}
|
||||
|
||||
public class PktmonData
|
||||
{
|
||||
public ushort eventID = 0; // 160 for the normal record type or 170 for the drop record type
|
||||
public ulong PktGroupId = 0;
|
||||
public ushort PktNumber = 0;
|
||||
public ushort AppearanceCount = 0;
|
||||
public ushort DirTag = 0;
|
||||
public ushort PacketType = 0;
|
||||
public ushort ComponentId = 0;
|
||||
public ushort EdgeId = 0;
|
||||
public ushort FilterId = 0;
|
||||
public uint DropReason = 0; // drop reason and location only applies to eventID 170
|
||||
public uint DropLocation = 0;
|
||||
public ushort OriginalPayloadSize = 0;
|
||||
public ushort LoggedPayloadSize = 0;
|
||||
}
|
||||
public class DomainController // constructed in DomainControllerParser
|
||||
{
|
||||
public uint IP = 0;
|
||||
|
@ -139,6 +164,40 @@ namespace SQLNA
|
|||
public ushort maxKeepAliveRetransmitsInARow = 0;
|
||||
}
|
||||
|
||||
public class PktmonDropConnectionData
|
||||
{
|
||||
public string clientIP = null;
|
||||
public ushort sourcePort = 0;
|
||||
public bool isIPV6 = false;
|
||||
public int frames = 0;
|
||||
public uint dropFrame = 0;
|
||||
public int firstFile = 0;
|
||||
public int lastFile = 0;
|
||||
public long startOffset = 0;
|
||||
public long endOffset = 0;
|
||||
public long endTicks = 0;
|
||||
public long duration = 0;
|
||||
public uint dropComponent = 0;
|
||||
public string dropReason = "";
|
||||
}
|
||||
|
||||
public class PktmonDelayConnectionData
|
||||
{
|
||||
public string sourceIP = null;
|
||||
public ushort sourcePort = 0;
|
||||
public string destIP = null;
|
||||
public ushort destPort = 0;
|
||||
public bool isIPV6 = false;
|
||||
public string protocolName = null; // TCP/UDP/SQL/SSRP/KERBEROS/etc. The same as in the CSV file
|
||||
public uint delayFrame = 0;
|
||||
public int delayFile = 0;
|
||||
public long delayOffset = 0;
|
||||
public long delayTicks = 0;
|
||||
public long delayDuration = 0;
|
||||
public uint delayStartComponent = 0;
|
||||
public uint delayEndComponent = 0;
|
||||
}
|
||||
|
||||
public class FailedConnectionData
|
||||
{
|
||||
public string clientIP = null;
|
||||
|
|
|
@ -201,12 +201,11 @@ namespace SQLNA
|
|||
|
||||
foreach (FrameData fd in c.frames)
|
||||
{
|
||||
if (fd.pktmon != null & fd.pktmon.AppearanceCount > 1) continue; // don't parse pktmon tracepoints more than once
|
||||
try
|
||||
{
|
||||
// increased < 7 to < 9. to fix exception in TCP ETW *** TODO TODO TODO
|
||||
// weed out non-TDS packets
|
||||
//if (fd.payloadLength < 7) continue; // ATTENTION payload is 8 bytes *** should we be < 8 instead ??? *** TODO TODO TODO
|
||||
if (fd.payloadLength < 9 ) continue; // ATTENTION payload is 8 bytes
|
||||
if (fd.payloadLength < 8 ) continue; // ATTENTION payload is 8 bytes
|
||||
|
||||
// ignore continuation packets until we get a complete TDS message parser built
|
||||
if (fd.isContinuation) continue;
|
||||
|
@ -704,9 +703,7 @@ namespace SQLNA
|
|||
if (c.isSQL)
|
||||
{
|
||||
SQLServer Server = trace.GetSQLServer(c.destIP, c.destIPHi, c.destIPLo, c.destPort, c.isIPV6);
|
||||
Server.conversations.Add(c);
|
||||
if (Server.serverVersion == "" && c.serverVersion != null) Server.serverVersion = c.serverVersion;
|
||||
if (Server.sqlHostName == "" && c.serverName != null) Server.sqlHostName = c.serverName;
|
||||
Server.AddConversation(c);
|
||||
}
|
||||
} // for each conversation
|
||||
}
|
||||
|
@ -723,11 +720,11 @@ namespace SQLNA
|
|||
if (server != null)
|
||||
{
|
||||
c.isSQL = true;
|
||||
server.conversations.Add(c);
|
||||
if (c.destIP != server.sqlIP || c.destIPHi != server.sqlIPHi || c.destIPLo != server.sqlIPLo || c.destPort != server.sqlPort)
|
||||
{
|
||||
reverseSourceDest(c);
|
||||
}
|
||||
server.AddConversation(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -748,7 +745,7 @@ namespace SQLNA
|
|||
if (s2 != null)
|
||||
{
|
||||
reverseSourceDest(c);
|
||||
s2.conversations.Add(c);
|
||||
s2.AddConversation(c);
|
||||
s.conversations.Remove(c);
|
||||
s.sqlIP = 0;
|
||||
s.sqlIPHi = 0;
|
||||
|
@ -757,7 +754,7 @@ namespace SQLNA
|
|||
}
|
||||
}
|
||||
}
|
||||
// remove bad entries from the trace.sqlServers collection - iterate backwards because of Remove method
|
||||
// remove bad entries from the trace.sqlServers collection - iterate backwards because of RemoveAt method
|
||||
for (int i = trace.sqlServers.Count - 1; i > 0; i--)
|
||||
{
|
||||
SQLServer s = (SQLServer)(trace.sqlServers[i]);
|
||||
|
@ -819,37 +816,51 @@ namespace SQLNA
|
|||
// helper function
|
||||
public static void reverseSourceDest(ConversationData c)
|
||||
{
|
||||
//Reverse isFromClient Flag in every frame.
|
||||
foreach (FrameData frameData in c.frames)
|
||||
frameData.isFromClient = !(frameData.isFromClient);
|
||||
//Reverse isFromClient Flag in every frame.
|
||||
foreach (FrameData frameData in c.frames)
|
||||
{
|
||||
if (frameData.pktmon != null)
|
||||
{
|
||||
foreach (FrameData fd in frameData.pktmonComponentFrames)
|
||||
{
|
||||
fd.isFromClient = !(fd.isFromClient);
|
||||
}
|
||||
// Reversing frameData.isFromClient, as in the else clause, is to be avoided as the main frame is the first element of the pktmonComponentFrames ArrayList.
|
||||
// Reversing it there, reverses it in the main record. Do not want to do that twice or it would undo the change.
|
||||
}
|
||||
else
|
||||
{
|
||||
frameData.isFromClient = !(frameData.isFromClient);
|
||||
}
|
||||
}
|
||||
|
||||
// reverse client and dest fields so that SQL ends up on the destination side of things
|
||||
ulong temp = 0;
|
||||
// reverse client and dest fields so that SQL ends up on the destination side of things
|
||||
ulong temp = 0;
|
||||
|
||||
temp = c.sourceMAC;
|
||||
c.sourceMAC = c.destMAC;
|
||||
c.destMAC = c.sourceMAC;
|
||||
temp = c.sourceMAC;
|
||||
c.sourceMAC = c.destMAC;
|
||||
c.destMAC = c.sourceMAC;
|
||||
|
||||
temp = c.sourceIP;
|
||||
c.sourceIP = c.destIP;
|
||||
c.destIP = (uint)temp;
|
||||
temp = c.sourceIP;
|
||||
c.sourceIP = c.destIP;
|
||||
c.destIP = (uint)temp;
|
||||
|
||||
temp = c.sourceIPHi;
|
||||
c.sourceIPHi = c.destIPHi;
|
||||
c.destIPHi = temp;
|
||||
temp = c.sourceIPHi;
|
||||
c.sourceIPHi = c.destIPHi;
|
||||
c.destIPHi = temp;
|
||||
|
||||
|
||||
temp = c.sourceIPLo;
|
||||
c.sourceIPLo = c.destIPLo;
|
||||
c.destIPLo = temp;
|
||||
temp = c.sourceIPLo;
|
||||
c.sourceIPLo = c.destIPLo;
|
||||
c.destIPLo = temp;
|
||||
|
||||
temp = c.sourcePort;
|
||||
c.sourcePort = c.destPort;
|
||||
c.destPort = (ushort)temp;
|
||||
temp = c.sourcePort;
|
||||
c.sourcePort = c.destPort;
|
||||
c.destPort = (ushort)temp;
|
||||
|
||||
temp = c.sourceFrames;
|
||||
c.sourceFrames = c.destFrames;
|
||||
c.destFrames = (uint)temp;
|
||||
temp = c.sourceFrames;
|
||||
c.sourceFrames = c.destFrames;
|
||||
c.destFrames = (uint)temp;
|
||||
}
|
||||
|
||||
//Parse User name and domain name
|
||||
|
|
Загрузка…
Ссылка в новой задаче