windows-nt/Source/XPSP1/NT/net/tcpip/tools/tcbview/tcbview.c
2020-09-26 16:20:57 +08:00

584 lines
18 KiB
C

/*++
Copyright (c) 1999, Microsoft Corporation
Module Name:
tcbview.c
Abstract:
This module contains code for a utility program which monitors
the variables for the active TCP/IP control blocks in the system.
The program optionally maintains a log for a specified TCB
in CSV format in a file specified by the user.
Author:
Abolade Gbadegesin (aboladeg) January-25-1999
Revision History:
--*/
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <commctrl.h>
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <fcntl.h>
#include <ntddip.h>
#include <ntddtcp.h>
#include <ipinfo.h>
#include <iphlpapi.h>
#include <iphlpstk.h>
//
// GLOBAL DATA
//
ULONG DisplayInterval = 500;
HWND ListHandle;
SOCKADDR_IN LogLocal;
FILE* LogFile = NULL;
PCHAR LogPath;
SOCKADDR_IN LogRemote;
HANDLE TcpipHandle;
UINT_PTR TimerId;
typedef enum {
LocalAddressColumn,
LocalPortColumn,
RemoteAddressColumn,
RemotePortColumn,
SmRttColumn,
DeltaColumn,
RtoColumn,
RexmitColumn,
RexmitCntColumn,
MaximumColumn
} LIST_COLUMNS;
CHAR* ColumnText[] = {
"LocalAddress",
"LocalPort",
"RemoteAddress",
"RemotePort",
"SmRtt",
"Delta",
"Rto",
"Rexmit",
"RexmitCnt",
};
VOID
AllocateConsole(
VOID
)
{
INT OsfHandle;
FILE* FileHandle;
//
// Being a GUI application, we do not have a console for our process.
// Allocate a console now, and make it our standard-output file.
//
AllocConsole();
OsfHandle = _open_osfhandle((intptr_t)GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT);
FileHandle = _fdopen(OsfHandle, "w");
if (!FileHandle) {
perror("_fdopen");
exit(0);
}
*stdout = *FileHandle;
setvbuf(stdout, NULL, _IONBF, 0);
}
LRESULT CALLBACK
DisplayWndProc(
HWND WindowHandle,
UINT Message,
WPARAM Wparam,
LPARAM Lparam
)
{
//
// Handle the few window-messages that we care about.
// Our window will contain a listview as soon as we've initialized,
// and we always resize that listview to fill our client area.
// We also set up a timer which periodically triggers refresh
// of the TCBs displayed.
//
if (Message == WM_CREATE) {
CREATESTRUCT* CreateStruct = (CREATESTRUCT*)Lparam;
LVCOLUMN LvColumn;
RECT rc;
do {
//
// Create the child listview, and insert columns
// for each of the TCB fields that we'll be displaying.
//
GetClientRect(WindowHandle, &rc);
ListHandle =
CreateWindowEx(
0,
WC_LISTVIEW,
NULL,
WS_CHILD|LVS_REPORT|LVS_NOSORTHEADER,
0,
0,
rc.right,
rc.bottom,
WindowHandle,
NULL,
CreateStruct->hInstance,
NULL
);
if (!ListHandle) { break; }
ZeroMemory(&LvColumn, sizeof(LvColumn));
for (; LvColumn.iSubItem < MaximumColumn; LvColumn.iSubItem++) {
LvColumn.mask = LVCF_FMT|LVCF_SUBITEM|LVCF_TEXT|LVCF_WIDTH;
LvColumn.fmt = LVCFMT_LEFT;
LvColumn.pszText = ColumnText[LvColumn.iSubItem];
LvColumn.cx = 50;
ListView_InsertColumn(ListHandle, LvColumn.iSubItem, &LvColumn);
}
//
// Initialize our periodic timer, and display our window.
//
TimerId = SetTimer(WindowHandle, 1, DisplayInterval, NULL);
ShowWindow(WindowHandle, SW_SHOW);
ShowWindow(ListHandle, SW_SHOW);
if (!TimerId) { break; }
return 0;
} while(FALSE);
PostQuitMessage(0);
return (LRESULT)-1;
} else if (Message == WM_DESTROY) {
//
// Stop our periodic timer, close the log-file (if any),
// close the handle on which we communicate with the TCP/IP driver,
// and post a quit message to cause the message-loop of our process
// to end.
//
KillTimer(WindowHandle, TimerId);
if (LogFile) { fclose(LogFile); }
NtClose(TcpipHandle);
PostQuitMessage(0);
return 0;
} else if (Message == WM_SETFOCUS) {
//
// Always pass the focus to our child-control, the listview.
//
SetFocus(ListHandle);
return 0;
} else if (Message == WM_WINDOWPOSCHANGED) {
RECT rc;
//
// Always resize our listview to fill our client-area.
//
GetClientRect(WindowHandle, &rc);
SetWindowPos(
ListHandle,
WindowHandle,
0,
0,
rc.right,
rc.bottom,
((WINDOWPOS*)Lparam)->flags
);
return 0;
} else if (Message == WM_TIMER) {
COORD Coord = {0, 0};
DWORD Error;
ULONG i;
LONG Item;
ULONG Length;
LVITEM LvItem;
CHAR Text[20];
TCP_FINDTCB_REQUEST Request;
TCP_FINDTCB_RESPONSE Response;
PMIB_TCPTABLE Table;
//
// If we're configured to use a log-file and we haven't created one,
// do so now, and print the CSV header to the file.
//
if (LogPath && !LogFile) {
LogFile = fopen(LogPath, "w+");
if (!LogFile) {
return 0;
} else {
fprintf(
LogFile,
"#senduna,sendnext,sendmax,sendwin,unacked,maxwin,cwin,"
"mss,rtt,smrtt,rexmitcnt,rexmittimer,rexmit,retrans,state,"
"flags,rto,delta\n"
);
}
}
//
// Clear our listview and retrieve a new table of TCP connections.
// It would be less visually jarring if, instead of deleting all
// the list-items each time, we used a mark-and-sweep to just update
// the ones which had changed. However, that sounds too much like work.
//
ListView_DeleteAllItems(ListHandle);
Error =
AllocateAndGetTcpTableFromStack(
&Table,
TRUE,
GetProcessHeap(),
0
);
if (Error) { return 0; }
//
// Display each active TCP control block in the listview.
// For each entry, we retrieve the partial TCB using IOCTL_TCP_FINDTCB,
// and then display it in the list.
// If we are generating a log-file for one of the TCBs,
// we append the current information to that log-file.
//
for (i = 0, Item = 0; i < Table->dwNumEntries; i++) {
if (Table->table[i].dwState < MIB_TCP_STATE_SYN_SENT ||
Table->table[i].dwState > MIB_TCP_STATE_TIME_WAIT) {
continue;
}
Request.Src = Table->table[i].dwLocalAddr;
Request.Dest = Table->table[i].dwRemoteAddr;
Request.SrcPort = (USHORT)Table->table[i].dwLocalPort;
Request.DestPort = (USHORT)Table->table[i].dwRemotePort;
ZeroMemory(&Response, sizeof(Response));
if (!DeviceIoControl(
TcpipHandle,
IOCTL_TCP_FINDTCB,
&Request,
sizeof(Request),
&Response,
sizeof(Response),
&Length,
NULL
)) {
continue;
}
lstrcpy(Text, inet_ntoa(*(PIN_ADDR)&Request.Src));
ZeroMemory(&LvItem, sizeof(LvItem));
LvItem.mask = LVIF_TEXT;
LvItem.iItem = Item;
LvItem.iSubItem = LocalAddressColumn;
LvItem.pszText = Text;
LvItem.iItem = ListView_InsertItem(ListHandle, &LvItem);
if (LvItem.iItem == -1) { continue; }
ListView_SetItemText(
ListHandle, Item, RemoteAddressColumn,
inet_ntoa(*(PIN_ADDR)&Request.Dest)
);
_ltoa(ntohs(Request.SrcPort), Text, 10);
ListView_SetItemText(ListHandle, Item, LocalPortColumn, Text);
_ltoa(ntohs(Request.DestPort), Text, 10);
ListView_SetItemText(ListHandle, Item, RemotePortColumn, Text);
_ltoa(Response.tcb_smrtt, Text, 10);
ListView_SetItemText(ListHandle, Item, SmRttColumn, Text);
_ltoa(0, /* Response.tcb_delta, */ Text, 10);
ListView_SetItemText(ListHandle, Item, DeltaColumn, Text);
wsprintf(
Text, "%d.%d", 0, // Response.tcb_rto / 10,
0 // (Response.tcb_rto % 10) * 100
);
ListView_SetItemText(ListHandle, Item, RtoColumn, Text);
_ltoa(Response.tcb_rexmit, Text, 10);
ListView_SetItemText(ListHandle, Item, RexmitColumn, Text);
_ltoa(Response.tcb_rexmitcnt, Text, 10);
ListView_SetItemText(ListHandle, Item, RexmitCntColumn, Text);
++Item;
//
// If we are generating a log-file, update it now.
// We allow the user to specify a wildcard for either or both port
// on the command-line, so if a wildcard was specified
// in 'LogLocal' or 'LogRemote', we now instantiate the wildcard
// for the first matching session.
//
if (Request.Src == LogLocal.sin_addr.s_addr &&
Request.Dest == LogRemote.sin_addr.s_addr &&
(LogLocal.sin_port == 0 ||
Request.SrcPort == LogLocal.sin_port) &&
(LogRemote.sin_port == 0 ||
Request.DestPort == LogRemote.sin_port)) {
//
// This assignment instantiates the user's wildcard, if any,
//
LogLocal.sin_port = Request.SrcPort;
LogRemote.sin_port = Request.DestPort;
fprintf(
LogFile, "%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,"
"%x,%u,%u\n",
Response.tcb_senduna,
Response.tcb_sendnext,
Response.tcb_sendmax,
Response.tcb_sendwin,
Response.tcb_unacked,
Response.tcb_maxwin,
Response.tcb_cwin,
Response.tcb_mss,
Response.tcb_rtt,
Response.tcb_smrtt,
Response.tcb_rexmitcnt,
Response.tcb_rexmittimer,
Response.tcb_rexmit,
Response.tcb_retrans,
Response.tcb_state,
0, // Response.tcb_flags,
0, // Response.tcb_rto,
0 // Response.tcb_delta
);
}
}
HeapFree(GetProcessHeap(), 0, Table);
UpdateWindow(ListHandle);
return 0;
}
return DefWindowProc(WindowHandle, Message, Wparam, Lparam);
}
void
DisplayUsage(
void
)
{
AllocateConsole();
printf("tcbview [-?] [-tcbhelp] [-refresh <ms>] [-log <path> <session>\n");
printf("\t<session> = <local endpoint> <remote endpoint>\n");
printf("\t<endpoint> = <address> { <port> | * }\n");
printf("Press <Ctrl-C> to continue...");
Sleep(INFINITE);
}
void
DisplayTcbHelp(
void
)
{
AllocateConsole();
printf("tcbview: TCB Help\n");
printf("tcb fields:\n");
printf("\tsenduna = seq. of first unack'd byte\n");
printf("\tsendnext = seq. of next byte to send\n");
printf("\tsendmax = max. seq. sent so far\n");
printf("\tsendwin = size of send window in bytes\n");
printf("\tunacked = number of unack'd bytes\n");
printf("\tmaxwin = max. send window offered\n");
printf("\tcwin = size of congestion window in bytes\n");
printf("\tmss = max. segment size\n");
printf("\trtt = timestamp of current rtt measurement\n");
printf("\tsmrtt = smoothed rtt measurement\n");
printf("\trexmitcnt = number of rexmit'd segments\n");
printf("\trexmittimer = rexmit timer in ticks\n");
printf("\trexmit = rexmit timeout last computed\n");
printf("\tretrans = total rexmit'd segments (all sessions)\n");
printf("\tstate = connection state\n");
printf("\tflags = connection flags (see below)\n");
printf("\trto = real-time rto (compare rexmit)\n");
printf("\tdelta = rtt variance\n");
printf("\n");
printf("flags:\n");
printf("\t00000001 = window explicitly set\n");
printf("\t00000002 = has client options\n");
printf("\t00000004 = from accept\n");
printf("\t00000008 = from active open\n");
printf("\t00000010 = client notified of disconnect\n");
printf("\t00000020 = in delayed action queue\n");
printf("\t00000040 = completing receives\n");
printf("\t00000080 = in receive-indication handler\n");
printf("\t00000100 = needs receive-completes\n");
printf("\t00000200 = needs to send ack\n");
printf("\t00000400 = needs to output\n");
printf("\t00000800 = delayed sending ack\n");
printf("\t00001000 = probing for path-mtu bh\n");
printf("\t00002000 = using bsd urgent semantics\n");
printf("\t00004000 = in 'DeliverUrgent'\n");
printf("\t00008000 = seen urgent data and urgent data fields valid\n");
printf("\t00010000 = needs to send fin\n");
printf("\t00020000 = using nagle's algorithm\n");
printf("\t00040000 = in 'TCPSend'\n");
printf("\t00080000 = flow-controlled (received zero-window)\n");
printf("\t00100000 = disconnect-notif. pending\n");
printf("\t00200000 = time-wait transition pending\n");
printf("\t00400000 = output being forced\n");
printf("\t00800000 = send pending after receive\n");
printf("\t01000000 = graceful-close pending\n");
printf("\t02000000 = keepalives enabled\n");
printf("\t04000000 = processing urgent data inline\n");
printf("\t08000000 = inform acd about connection\n");
printf("\t10000000 = fin sent since last retransmit\n");
printf("\t20000000 = unack'd fin sent\n");
printf("\t40000000 = need to send rst when closing\n");
printf("\t80000000 = in tcb table\n");
printf("Press <Ctrl-C> to continue...");
Sleep(INFINITE);
}
INT WINAPI
WinMain(
HINSTANCE InstanceHandle,
HINSTANCE Unused,
PCHAR CommandLine,
INT ShowWindowCode
)
{
LONG argc;
PCHAR* argv;
LONG i;
IO_STATUS_BLOCK IoStatus;
MSG Message;
OBJECT_ATTRIBUTES ObjectAttributes;
NTSTATUS Status;
HANDLE ThreadHandle;
ULONG ThreadId;
UNICODE_STRING UnicodeString;
HWND WindowHandle;
WNDCLASS WndClass;
//
// Process command-line arguments. See 'DisplayUsage' above for help.
//
argc = __argc;
argv = __argv;
for (i = 1; i < argc; i++) {
if (lstrcmpi(argv[i], "-?") == 0 || lstrcmpi(argv[i], "/?") == 0) {
DisplayUsage();
return 0;
} else if (lstrcmpi(argv[i], "-tcbhelp") == 0) {
DisplayTcbHelp();
return 0;
} else if (lstrcmpi(argv[i], "-refresh") == 0 && (i + 1) >= argc) {
DisplayInterval = atol(argv[++i]);
if (!DisplayInterval) {
DisplayUsage();
return 0;
}
} else if (lstrcmpi(argv[i], "-log") == 0) {
if ((i + 5) >= argc) {
DisplayUsage();
return 0;
}
LogPath = argv[++i];
LogLocal.sin_addr.s_addr = inet_addr(argv[++i]);
if (lstrcmpi(argv[i+1], "*") == 0) {
LogLocal.sin_port = 0; ++i;
} else {
LogLocal.sin_port = htons((SHORT)atol(argv[++i]));
}
LogRemote.sin_addr.s_addr = inet_addr(argv[++i]);
if (lstrcmpi(argv[i+1], "*") == 0) {
LogRemote.sin_port = 0; ++i;
} else {
LogRemote.sin_port = htons((SHORT)atol(argv[++i]));
}
if (LogLocal.sin_addr.s_addr == INADDR_NONE ||
LogRemote.sin_addr.s_addr == INADDR_NONE) {
DisplayUsage();
return 0;
}
}
}
//
// Open a handle to the TCP/IP driver,
// to be used in issuing IOCTL_TCP_FINDTCB requests.
//
RtlInitUnicodeString(&UnicodeString, DD_TCP_DEVICE_NAME);
InitializeObjectAttributes(
&ObjectAttributes,
&UnicodeString,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
Status =
NtCreateFile(
&TcpipHandle,
GENERIC_EXECUTE,
&ObjectAttributes,
&IoStatus,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ|FILE_SHARE_WRITE,
FILE_OPEN_IF,
0,
NULL,
0
);
if (!NT_SUCCESS(Status)) {
printf("NtCreateFile: %x\n", Status);
return 0;
}
//
// Register our window class and create the sole instance
// of our main window. Then, enter our application message loop
// until the user dismisses the window.
//
ZeroMemory(&WndClass, sizeof(WndClass));
WndClass.lpfnWndProc = DisplayWndProc;
WndClass.hInstance = InstanceHandle;
WndClass.lpszClassName = "TcbViewClass";
Message.wParam = 0;
if (!RegisterClass(&WndClass)) {
printf("RegisterClass: %d\n", GetLastError());
} else {
WindowHandle =
CreateWindowEx(
0,
"TcbViewClass",
"TcbView",
WS_TILEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
InstanceHandle,
NULL
);
if (!WindowHandle) {
printf("CreateWindowEx: %d\n", GetLastError());
} else {
while(GetMessage(&Message, NULL, 0, 0)) {
TranslateMessage(&Message);
DispatchMessage(&Message);
}
}
}
return (LONG)Message.wParam;
}