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:
Malcolm Stewart 2022-02-07 12:38:48 -05:00
Родитель 7045a71b00
Коммит d58441d361
15 изменённых файлов: 871 добавлений и 187 удалений

Двоичный файл не отображается.

Просмотреть файл

@ -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