Skip to content

Commit 3839ecb

Browse files
SQLNA Improvements
Display in CSV file the InstOpt argument send by the client in the PreLogin packet. Display in CSV file the ConnectionID sent by the client in the PreLogin packet. New report showing connections with Zero Window packet. Updated the packet visualization showing Zero Windows (ZW) packets and Zero Window Probe (ZWP) packets.
1 parent 4cdc835 commit 3839ecb

File tree

11 files changed

+280
-9
lines changed

11 files changed

+280
-9
lines changed
-1 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

SQL_Network_Analyzer/SQLNA/ConversationData.cs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ public class ConversationData // - constructed in GetI
7777
public bool hasTDS = false; // - set in ProcessTDS
7878
public bool hasTDS8 = false; // - set in ProcessTDS
7979
public bool isSQL = false; // - set in ProcessTDS
80-
public bool isEncrypted = false; // - set in GetServerPreloginInfo
81-
public bool isEncRequired = false; // - set in
82-
public bool isMARSEnabled = false; // - set in ProcessTDS - in PreLogin packet
80+
public bool isEncrypted = false; // - set in GetServerPreloginInfo and GetClientPreloginInfo
81+
public bool isEncRequired = false; // - set in GetServerPreloginInfo
82+
public bool isMARSEnabled = false; // - set in GetClientPreloginInfo
8383
public bool hasPrelogin = false; // - set in ProcessTDS
8484
public bool hasPreloginResponse = false; // - set in ProcessTDS
8585
public bool hasClientSSL = false; // - set in ProcessTDS
@@ -95,6 +95,9 @@ public class ConversationData // - constructed in GetI
9595
public bool hasIntegratedSecurity = false; // - set in ProcessTDS
9696
public bool hasPostLoginResponse = false; // - set in ProcessTDS - this contains the ENVCHANGE token - login was a success
9797
public bool hasDiffieHellman = false; // - set in ProcessTDS
98+
public bool hasClientZeroWindow = false; // - set in ParseTCPFrame
99+
public bool hasServerZeroWindow = false; // - set in ParseTCPFrame
100+
public int zeroWindowCount = 0; // - set in ParseTCPFrame
98101
public int smpSynCount = 0; // - accumulated in ParseTCPFrame
99102
public int smpAckCount = 0; // - accumulated in ParseTCPFrame
100103
public int smpFinCount = 0; // - accumulated in ParseTCPFrame
@@ -417,7 +420,39 @@ public string loginFlags
417420
public string GetPacketList(int start, int length)
418421
{
419422
string s = "";
420-
for (int i = start; i < start + length; i++) s += ((FrameData)frames[i]).PacketTypeAndDirection + " ";
423+
FrameData f = null;
424+
string frameType = "";
425+
426+
// Are we dealing with a Zero Window packet of not
427+
bool fzwMode = false;
428+
bool fzwFromClient = false;
429+
for (int i = start; i < start + length; i++)
430+
{
431+
f = (FrameData)frames[i];
432+
frameType = f.PacketTypeAndDirection + " ";
433+
434+
if (f.isZeroWindowPacket) // could flip from client to server - not very likely
435+
{
436+
fzwMode = true;
437+
fzwFromClient = f.isFromClient;
438+
}
439+
else if (fzwMode)
440+
{
441+
if (fzwFromClient == f.isFromClient)
442+
{
443+
if (f.windowSize > 0) fzwMode = false; // exit Zero Window Mode
444+
}
445+
else
446+
{
447+
if (f.isKeepAlive)
448+
{
449+
frameType = (f.isFromClient ? ">" : "<") + "ZWP "; // Zero Window Probe - looks like a Keep-Alive packet but occurs after a Zero Packet
450+
}
451+
}
452+
}
453+
454+
s += frameType;
455+
}
421456
return s.TrimEnd();
422457
}
423458

SQL_Network_Analyzer/SQLNA/FrameData.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public bool isKeepAlive
103103
get
104104
{
105105
return ((payloadLength == 1) &&
106-
(payload[0] == 0) &&
106+
// (payload[0] == 0) && // not true in 100% of cases
107107
((flags & (byte)TCPFlag.ACK) != 0) &&
108108
((flags & (byte)(TCPFlag.FIN | TCPFlag.FIN | TCPFlag.SYN | TCPFlag.RESET | TCPFlag.PUSH)) == 0));
109109
}
@@ -134,10 +134,16 @@ public bool hasRESETFlag
134134
get { return (flags & (byte)TCPFlag.RESET) != 0; }
135135
}
136136

137+
public bool isZeroWindowPacket
138+
{
139+
get { return hasACKFlag && !hasSYNFlag && !hasFINFlag && !hasRESETFlag && windowSize == 0; }
140+
}
141+
137142
public string PacketType
138143
{
139144
get
140145
{
146+
if (isZeroWindowPacket) return "ZW";
141147
if (isKeepAlive) return "KA";
142148
if (isRetransmit && payloadLength > 1) return "RET";
143149

@@ -165,7 +171,7 @@ public string PacketType
165171
case FrameType.SSPI: return "SS";
166172
case FrameType.TabularResponse: return "DATA";
167173
case FrameType.XactMgrRequest: return "TX";
168-
default: return FormatFlags("");
174+
default: return FormatFlags(""); // "" means no dots between flag letters
169175
}
170176
}
171177
}

SQL_Network_Analyzer/SQLNA/OutputText.cs

Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public static void TextReport(NetworkTrace Trace)
2929
if (Program.outputConversationList) DisplaySucessfulLoginReport(Trace); // optional section; must be explicitly requested
3030
DisplayResetConnections(Trace);
3131
DisplayServerClosedConnections(Trace);
32+
DisplayZeroWindowConnections(Trace);
3233
DisplayPktmonDrops(Trace);
3334
DisplayBadConnections(Trace);
3435
DisplayLoginErrors(Trace);
@@ -987,6 +988,196 @@ private static void DisplayServerClosedConnections(NetworkTrace Trace)
987988
}
988989
}
989990

991+
private static void DisplayZeroWindowConnections(NetworkTrace Trace)
992+
{
993+
bool hasError = false;
994+
995+
long firstTick = 0;
996+
long lastTick = 0;
997+
998+
if (Trace.frames != null && Trace.frames.Count > 0)
999+
{
1000+
firstTick = ((FrameData)Trace.frames[0]).ticks;
1001+
lastTick = ((FrameData)Trace.frames[Trace.frames.Count - 1]).ticks;
1002+
}
1003+
1004+
foreach (SQLServer s in Trace.sqlServers)
1005+
{
1006+
if (s.hasZeroWindow)
1007+
{
1008+
hasError = true;
1009+
List<ZeroWindowData> ZeroWindowRecords = new List<ZeroWindowData>();
1010+
1011+
// initialize graph object
1012+
TextGraph g = new TextGraph();
1013+
g.startTime = new DateTime(firstTick);
1014+
g.endTime = new DateTime(lastTick);
1015+
g.SetGraphWidth(150);
1016+
g.fAbsoluteScale = true;
1017+
g.SetCutoffValues(1, 3, 9, 27, 81);
1018+
1019+
string sqlIP = (s.isIPV6) ? utility.FormatIPV6Address(s.sqlIPHi, s.sqlIPLo) : utility.FormatIPV4Address(s.sqlIP);
1020+
1021+
foreach (ConversationData c in s.conversations)
1022+
{
1023+
if (c.hasClientZeroWindow || c.hasServerZeroWindow)
1024+
{
1025+
ZeroWindowData zwd = new ZeroWindowData();
1026+
1027+
zwd.clientIP = (c.isIPV6) ? utility.FormatIPV6Address(c.sourceIPHi, c.sourceIPLo) : utility.FormatIPV4Address(c.sourceIP);
1028+
zwd.sourcePort = c.sourcePort;
1029+
zwd.isIPV6 = c.isIPV6;
1030+
zwd.frames = c.frames.Count;
1031+
zwd.zwFrame = 0;
1032+
zwd.zwFile = 0;
1033+
zwd.zwFromClient = c.hasClientZeroWindow;
1034+
zwd.zwFromServer = c.hasServerZeroWindow;
1035+
zwd.zwCount = c.zeroWindowCount;
1036+
zwd.firstFile = Trace.files.IndexOf(((FrameData)(c.frames[0])).file);
1037+
zwd.lastFile = Trace.files.IndexOf(((FrameData)(c.frames[c.frames.Count - 1])).file);
1038+
zwd.startOffset = ((FrameData)c.frames[0]).ticks - firstTick;
1039+
zwd.endTicks = ((FrameData)c.frames[c.frames.Count - 1]).ticks;
1040+
zwd.endOffset = zwd.endTicks - firstTick;
1041+
zwd.duration = zwd.endOffset - zwd.startOffset;
1042+
zwd.endFrames = c.GetLastPacketList(20);
1043+
1044+
for (int i = c.frames.Count - 1; i >= 0; i--) // search for the last Zero Window frame
1045+
{
1046+
FrameData f = (FrameData)c.frames[i];
1047+
1048+
if (f.isZeroWindowPacket)
1049+
{
1050+
zwd.zwFrame = f.frameNo;
1051+
zwd.zwFile = Trace.files.IndexOf(f.file);
1052+
g.AddData(new DateTime(f.ticks), 1.0); // for graphing
1053+
break;
1054+
}
1055+
}
1056+
1057+
ZeroWindowRecords.Add(zwd);
1058+
}
1059+
}
1060+
1061+
if (ZeroWindowRecords.Count > 0)
1062+
{
1063+
Program.logMessage("The following conversations with SQL Server " + sqlIP + " on port " + s.sqlPort + " had conversations with a Zero Window record:\r\n");
1064+
ReportFormatter rf = new ReportFormatter();
1065+
switch (Program.filterFormat)
1066+
{
1067+
case "N":
1068+
{
1069+
rf.SetColumnNames("NETMON Filter (Client conv.):L", "Files:R", "ZW File:R", "ZW Frame:R", "ZW Count:R", "ZW Client:R", "ZW Server:R", "Start Offset:R", "End Offset:R", "End Time:R", "Frames:R", "Duration:R", "End Frames:L");
1070+
break;
1071+
}
1072+
case "W":
1073+
{
1074+
rf.SetColumnNames("WireShark Filter (Client conv.):L", "Files:R", "ZW File:R", "ZW Frame:R", "ZW Count:R", "ZW Client:R", "ZW Server:R", "Start Offset:R", "End Offset:R", "End Time:R", "Frames:R", "Duration:R", "End Frames:L");
1075+
break;
1076+
}
1077+
default:
1078+
{
1079+
rf.SetColumnNames("Client Address:L", "Port:R", "Files:R", "ZW File:R", "ZW Frame:R", "ZW Count:R", "ZW Client:R", "ZW Server:R", "Start Offset:R", "End Offset:R", "End Time:R", "Frames:R", "Duration:R", "End Frames:L");
1080+
break;
1081+
}
1082+
}
1083+
1084+
var OrderedRows = from row in ZeroWindowRecords orderby row.endOffset ascending select row;
1085+
1086+
foreach (var row in OrderedRows)
1087+
{
1088+
switch (Program.filterFormat)
1089+
{
1090+
case "N": // list client IP and port as a NETMON filter string
1091+
{
1092+
rf.SetcolumnData((row.isIPV6 ? "IPV6" : "IPV4") + ".Address==" + row.clientIP + " and tcp.port==" + row.sourcePort.ToString(),
1093+
(row.firstFile == row.lastFile) ? row.firstFile.ToString() : row.firstFile + "-" + row.lastFile,
1094+
row.zwFile.ToString(),
1095+
row.zwFrame.ToString(),
1096+
row.zwCount.ToString(),
1097+
(row.zwFromClient ? "Y" : ""),
1098+
(row.zwFromServer ? "Y" : ""),
1099+
(row.startOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
1100+
(row.endOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
1101+
new DateTime(row.endTicks).ToString(utility.TIME_FORMAT),
1102+
row.frames.ToString(),
1103+
(row.duration / utility.TICKS_PER_SECOND).ToString("0.000000"),
1104+
row.endFrames);
1105+
break;
1106+
}
1107+
case "W": // list client IP and port as a WireShark filter string
1108+
{
1109+
rf.SetcolumnData((row.isIPV6 ? "ipv6" : "ip") + ".addr==" + row.clientIP + " and tcp.port==" + row.sourcePort.ToString(),
1110+
(row.firstFile == row.lastFile) ? row.firstFile.ToString() : row.firstFile + "-" + row.lastFile,
1111+
row.zwFile.ToString(),
1112+
row.zwFrame.ToString(),
1113+
row.zwCount.ToString(),
1114+
(row.zwFromClient ? "Y" : ""),
1115+
(row.zwFromServer ? "Y" : ""),
1116+
(row.startOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
1117+
(row.endOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
1118+
new DateTime(row.endTicks).ToString(utility.TIME_FORMAT),
1119+
row.frames.ToString(),
1120+
(row.duration / utility.TICKS_PER_SECOND).ToString("0.000000"),
1121+
row.endFrames);
1122+
break;
1123+
}
1124+
default: // list client IP and port as separate columns
1125+
{
1126+
rf.SetcolumnData(row.clientIP,
1127+
row.sourcePort.ToString(),
1128+
(row.firstFile == row.lastFile) ? row.firstFile.ToString() : row.firstFile + "-" + row.lastFile,
1129+
row.zwFile.ToString(),
1130+
row.zwFrame.ToString(),
1131+
row.zwCount.ToString(),
1132+
(row.zwFromClient ? "Y" : ""),
1133+
(row.zwFromServer ? "Y" : ""),
1134+
(row.startOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
1135+
(row.endOffset / utility.TICKS_PER_SECOND).ToString("0.000000"),
1136+
new DateTime(row.endTicks).ToString(utility.TIME_FORMAT),
1137+
row.frames.ToString(),
1138+
(row.duration / utility.TICKS_PER_SECOND).ToString("0.000000"),
1139+
row.endFrames);
1140+
break;
1141+
}
1142+
}
1143+
}
1144+
1145+
Program.logMessage(rf.GetHeaderText());
1146+
Program.logMessage(rf.GetSeparatorText());
1147+
1148+
for (int i = 0; i < rf.GetRowCount(); i++)
1149+
{
1150+
Program.logMessage(rf.GetDataText(i));
1151+
}
1152+
1153+
Program.logMessage();
1154+
1155+
//
1156+
// Display graph
1157+
//
1158+
1159+
Program.logMessage(" Distribution of Zero-Window conversations.");
1160+
Program.logMessage();
1161+
g.ProcessData();
1162+
Program.logMessage(" " + g.GetLine(0));
1163+
Program.logMessage(" " + g.GetLine(1));
1164+
Program.logMessage(" " + g.GetLine(2));
1165+
Program.logMessage(" " + g.GetLine(3));
1166+
Program.logMessage(" " + g.GetLine(4));
1167+
Program.logMessage(" " + g.GetLine(5));
1168+
1169+
Program.logMessage();
1170+
}
1171+
}
1172+
}
1173+
1174+
if (hasError == false)
1175+
{
1176+
Program.logMessage("No Zero-Window SQL conversations found.");
1177+
Program.logMessage();
1178+
}
1179+
}
1180+
9901181

9911182
private static void DisplayPktmonDrops(NetworkTrace Trace)
9921183
{
@@ -3517,7 +3708,7 @@ private static void DisplayFooter()
35173708

35183709
private static void OutputStats(NetworkTrace Trace)
35193710
{
3520-
Program.logStat(@"SourceIP,SourcePort,DestIP,DestPort,IPVersion,Protocol,Syn,Fin,Reset,AckSynDelayms,Retransmit,ClientDup,ServerDup,KeepAlive,Integrated Login,NTLM,Login7,Encrypted,Mars,PacketVisualization,Pktmon,MaxPktmonDelay,PktmonDrop,PktmonDropReason,MaxPayloadSize,PayloadSizeLimit,Frames,Bytes,SentBytes,ReceivedBytes,Bytes/Sec,StartFile,EndFile,StartTime,EndTime,Duration,ClientTTL,ClientLowHops,ServerTTL,ServerLowHops,ConnectionID,ServerName,InstOpt,ServerVersion,DatabaseName,ServerTDSVersion,ClientTDSVersion,ServerTLSVersion,ClientTLSVersion,RedirSrv,RedirPort,Error,ErrorState,ErrorMessage,");
3711+
Program.logStat(@"SourceIP,SourcePort,DestIP,DestPort,IPVersion,Protocol,Syn,Fin,Reset,ZeroWindow,AckSynDelayms,Retransmit,ClientDup,ServerDup,KeepAlive,Integrated Login,NTLM,Login7,Encrypted,Mars,PacketVisualization,Pktmon,MaxPktmonDelay,PktmonDrop,PktmonDropReason,MaxPayloadSize,PayloadSizeLimit,Frames,Bytes,SentBytes,ReceivedBytes,Bytes/Sec,StartFile,EndFile,StartTime,EndTime,Duration,ClientTTL,ClientLowHops,ServerTTL,ServerLowHops,ConnectionID,ServerName,InstOpt,ServerVersion,DatabaseName,ServerTDSVersion,ClientTDSVersion,ServerTLSVersion,ClientTLSVersion,RedirSrv,RedirPort,Error,ErrorState,ErrorMessage,");
35213712

35223713
long traceFirstTick = 0;
35233714
if (Trace.frames != null && Trace.frames.Count > 0)
@@ -3551,6 +3742,7 @@ private static void OutputStats(NetworkTrace Trace)
35513742
c.synCount + "," +
35523743
c.finCount + "," +
35533744
c.resetCount + "," +
3745+
(c.hasClientZeroWindow || c.hasServerZeroWindow ? "Y" : "") + "," +
35543746
(c.isUDP || c.ackSynTime == 0 ? "" : ((int)(c.LoginDelay("AS", firstTick) / utility.TICKS_PER_MILLISECOND)).ToString()) + "," +
35553747
c.rawRetransmits + "," +
35563748
c.duplicateClientPackets + "," +

SQL_Network_Analyzer/SQLNA/Parser.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,18 @@ public static void ParseTCPFrame(byte[] b, int offset, NetworkTrace t, FrameData
14921492
f.ackNo = utility.B2UInt32(b, offset + 8);
14931493
f.flags = b[offset + 13];
14941494
f.windowSize = utility.B2UInt16(b, offset + 14);
1495+
if (f.isZeroWindowPacket)
1496+
{
1497+
f.conversation.zeroWindowCount++;
1498+
if (f.isFromClient)
1499+
{
1500+
f.conversation.hasClientZeroWindow = true;
1501+
}
1502+
else
1503+
{
1504+
f.conversation.hasServerZeroWindow = true;
1505+
}
1506+
}
14951507
CheckSum = utility.B2UInt16(b, offset + 16);
14961508
if (utility.B2UInt16(b, offset + 18) != 0) canTestChecksum = false; // we only want to test if the Urgent flag is 0
14971509
if (f.hasSYNFlag)

0 commit comments

Comments
 (0)