Monitoring Task Functions

The framework I've built allows you to add network monitoring functions by creating extension DLLs that accept as their lone parameter a pointer to a MONITOR_CONTEXT structure. These extension DLLs can do whatever you need them to do, allowing for tremendous flexibility. I've provided four functions as examples.

  • Ping uses TCP/IP and "pings" another machine on the network.
  • TestServiceStatus checks the status of a service on a specified server.
  • TestODBCConnection tests an ODBC connection.
  • TestSQLServerStatus does more extensive, SQL Server-specific testing.

These four example functions are implemented in Listing 14-5, TestMonitor.cpp. When compiled, this listing creates TestMonitor.dll.

Listing 14-5

TestMonitor.cpp

 // TestMonitor.cpp : entry point for the example DLL called by //  MonitorService // #include "stdafx.h" BOOL APIENTRY DllMain(HANDLE hModule,                       DWORD  ul_reason_for_call,                       LPVOID lpReserved                      ) {     return TRUE; } // This should really be in a shared header file used by //  the service and this DLL. typedef struct _MONITOR_CONTEXT {     OperationType Operation;     ExecutableType exeType;     LPVOID CommHandle;     char szExecutable[255];     char szFunction[255];     char szParameters[4][132];     char szTaskName[255];     DWORD dwTaskID;     DWORD dwRunEveryInterval;     char atOrEvery;     char szReturnMsg[512];     long returnCode;     bool fSuccess; } MONITOR_CONTEXT; // Declarations and definitions for Ping // This is an adaptation of the Ping included in the //  Platform SDK. #define ICMP_ECHO 8 #define ICMP_ECHOREPLY 0 #define ICMP_MIN 8 // Minimum 8-byte icmp packet (just header) #define STATUS_FAILED 0xFFFF #define DEF_PACKET_SIZE 32 #define MAX_PACKET 1024 #pragma pack(4) /* The IP header */ typedef struct iphdr {     unsigned int h_len:4;          // Length of the header     unsigned int version:4;        // Version of IP     unsigned char tos;             // Type of service     unsigned short total_len;      // Total length of the packet     unsigned short ident;          // Unique identifier     unsigned short frag_and_flags; // Flags     unsigned char ttl;     unsigned char proto;           // Protocol (TCP, UDP, etc.)     unsigned short checksum;       // IP checksum     unsigned int sourceIP;     unsigned int destIP; }IpHeader; // ICMP header typedef struct _ihdr {     BYTE i_type;     BYTE i_code; /* type sub code */     USHORT i_cksum;     USHORT i_id;     USHORT i_seq;     // This is not the std header, but     //  we reserve space for time. This is     //  used to get the elapsed time.     ULONG timestamp; }IcmpHeader; //------------------------------------------------------------------- // Tests service status; considers RUNNING to be //  success. DWORD APIENTRY TestServiceStatus(LPVOID v) {     MONITOR_CONTEXT *Context;     char szServer[255];     char szService[255];     DWORD cbNeeded, dwServicesReturned, dwResumeHandle;     SC_HANDLE scm;     ENUM_SERVICE_STATUS *ess;     ess = new ENUM_SERVICE_STATUS[1000];     Context = (MONITOR_CONTEXT *)v;     strcpy(szServer, Context->szParameters[0]);     strcpy(szService, Context->szParameters[1]);     scm = OpenSCManager(szServer, NULL,         SC_MANAGER_ENUMERATE_SERVICE);     dwServicesReturned = dwResumeHandle = 0;     if (EnumServicesStatus(scm, SERVICE_WIN32, SERVICE_STATE_ALL,         ess, sizeof(ENUM_SERVICE_STATUS)*1000,         &cbNeeded, &dwServicesReturned, &dwResumeHandle) == 0)     {         return Context->fSuccess == true? 1 : 0;     }     for (DWORD loop = 0 ; loop < dwServicesReturned ; loop++)     {         if (!(stricmp(ess[loop].lpDisplayName, szService)))         {             if (ess[loop].ServiceStatus.dwCurrentState ==                 SERVICE_RUNNING)             {                 Context->fSuccess = true;                 strcpy(Context->szReturnMsg, "Service is running");             }             else             {                 Context->fSuccess = false;                 switch (ess[loop].ServiceStatus.dwCurrentState)                 {                 case SERVICE_START_PENDING:                     strcpy(Context->szReturnMsg,                         "Service is starting");                     break;                 case SERVICE_STOPPED:                     strcpy(Context->szReturnMsg,                         "Service is stopped");                     break;                 case SERVICE_PAUSE_PENDING:                     strcpy(Context->szReturnMsg,                         "Service is pausing");                     break;                 default:                     strcpy(Context->szReturnMsg,                         "Service NOT running");                     break;                 }             }             break;         }     }     CloseServiceHandle(scm);     return Context->fSuccess == true? 1 : 0; } //------------------------------------------------------------------- // Calculates checksum for Ping USHORT Checksum(USHORT *buffer, int size) {     unsigned long cksum = 0;     while(size > 1)     {         cksum += *buffer++;         size -= sizeof(USHORT);     }     if (size)     {         cksum += *(UCHAR*)buffer;     }     cksum = (cksum >> 16) + (cksum & 0xffff);     cksum += (cksum >> 16);     return (USHORT)(~cksum); } //------------------------------------------------------------------- // Fills in default ICMP header information void fill_icmp_data(char * icmp_data, int datasize) {     static int nCounter = 1;     IcmpHeader *icmp_hdr;     char *datapart;     icmp_hdr = (IcmpHeader*)icmp_data;     icmp_hdr->i_type = ICMP_ECHO;     icmp_hdr->i_code = 0;     icmp_hdr->i_id = (nCounter++);     icmp_hdr->i_cksum = 0;     icmp_hdr->i_seq = 0;     datapart = icmp_data + sizeof(IcmpHeader);     memset(datapart, 'D', datasize - sizeof(IcmpHeader)); } //------------------------------------------------------------------- // Used really just to determine the time it took for response int decode_resp(char *buf, int bytes, struct sockaddr_in *from) {     IpHeader *iphdr;     IcmpHeader *icmphdr;     unsigned short iphdrlen;     iphdr = (IpHeader *)buf;     iphdrlen = iphdr->h_len * 4 ; // Number of 32-bit words                                   //  * 4 = bytes     icmphdr = (IcmpHeader*)(buf + iphdrlen);     if (icmphdr->i_type != ICMP_ECHOREPLY)     {         return 0;     }     return(GetTickCount()-icmphdr->timestamp); } //------------------------------------------------------------------- // Exported Ping function. // Note that this function will NOT work on Microsoft Windows 95; //  it should work on Windows 98, Windows NT, and Windows 2000. int APIENTRY Ping(LPVOID v) {     MONITOR_CONTEXT *Context;     char            szServer[255];     SOCKET          rawSocket;     LPHOSTENT       lpHost;     struct          sockaddr_in saDest;     struct          sockaddr_in saSrc;     int             nRet;     int             result = 0;     int             iTimeout = 1000;     int             datasize;     int             recvLen;     WSADATA         wsaData;     WORD            wVerReq;     char            *icmp_data;     char            *recvbuf;     Context = (MONITOR_CONTEXT *)v;     Context->fSuccess = false;  // Default.     strcpy(szServer, Context->szParameters[0]);     wVerReq = MAKEWORD(2, 1);     WSAStartup(wVerReq, &wsaData);     rawSocket = WSASocket (AF_INET,         SOCK_RAW,         IPPROTO_ICMP,         NULL, 0, 0);     if (rawSocket == INVALID_SOCKET)     {         WSACleanup();         wsprintf(Context->szReturnMsg,             "Error creating Raw socket");         return(result);     }     iTimeout = 1000;     nRet = setsockopt(rawSocket, SOL_SOCKET, SO_RCVTIMEO,         (char*)&iTimeout, sizeof(iTimeout));     if (nRet == SOCKET_ERROR)     {         WSACleanup();         wsprintf(Context->szReturnMsg,             "Error setting socket option receive timeout");         return(result);     }     iTimeout = 1000;     nRet = setsockopt(rawSocket, SOL_SOCKET, SO_SNDTIMEO,         (char*)&iTimeout, sizeof(iTimeout));     if (nRet == SOCKET_ERROR)     {         WSACleanup();         wsprintf(Context->szReturnMsg,             "Error setting socket option send timeout");         return(result);     }     memset(&saDest, 0, sizeof(saDest));     // Look up host.     lpHost = gethostbyname(szServer);     if (lpHost == NULL)     {         wsprintf(Context->szReturnMsg,             "Error Getting host (%s) by name",             szServer);         return(result);     }     // Set up destination socket address.     saDest.sin_addr.s_addr = *((u_long FAR *) (lpHost->h_addr));     saDest.sin_family = AF_INET;     saDest.sin_port = 0;     datasize = DEF_PACKET_SIZE;     datasize += sizeof(IcmpHeader);     icmp_data = (char *)HeapAlloc(        GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PACKET);     recvbuf = (char *)HeapAlloc(        GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PACKET);     if (icmp_data == NULL  recvbuf == NULL)     {         wsprintf(Context->szReturnMsg,             "Error Getting memory");         return(result);     }     memset(icmp_data, 0, MAX_PACKET);     fill_icmp_data(icmp_data, datasize);     ((IcmpHeader*)icmp_data)->i_cksum = 0;     ((IcmpHeader*)icmp_data)->timestamp = GetTickCount();     ((IcmpHeader*)icmp_data)->i_seq = 1;     ((IcmpHeader*)icmp_data)->i_cksum = Checksum((USHORT*)icmp_data,         datasize);     nRet = sendto(rawSocket, icmp_data,         datasize, 0,         (struct sockaddr*)&saDest,         sizeof(saDest));     if (nRet == SOCKET_ERROR)     {         if (WSAGetLastError() == WSAETIMEDOUT)         {             wsprintf(Context->szReturnMsg,                 "Timeout in Ping");             return(result);         }         else         {             wsprintf(Context->szReturnMsg,                 "Error %d in Ping",                 WSAGetLastError());             return(result);         }     }     recvLen = sizeof(saSrc);     nRet = recvfrom(rawSocket,         recvbuf, MAX_PACKET, 0,         (struct sockaddr*)&saSrc,         &recvLen);     if (nRet == SOCKET_ERROR)     {         if (WSAGetLastError() == WSAETIMEDOUT)         {             wsprintf(Context->szReturnMsg,                 "Timeout in Ping");             return(result);         }         else         {             wsprintf(Context->szReturnMsg,                 "Error %d in Ping",                 WSAGetLastError());             return(result);         }     }     // Decode Response returns the time     //  in milliseconds the ping took.     wsprintf(Context->szReturnMsg,         "Ping succeeded - time: %d ms",         decode_resp(recvbuf, nRet, &saSrc));     Context->returnCode =         decode_resp(recvbuf, nRet, &saSrc);     Context->fSuccess = true;     HeapFree (GetProcessHeap(), 0, icmp_data);     HeapFree (GetProcessHeap(), 0, recvbuf);     if (rawSocket != INVALID_SOCKET)     {         closesocket(rawSocket);     }     WSACleanup();     return(1); } //------------------------------------------------------------------- // Exported function to test generic ODBC connection. //  Data source must be defined in ODBC administrator. int APIENTRY TestODBCConnection(PVOID v) {     MONITOR_CONTEXT *Context;     CODBCDatabase *db;     int result = 0;     Context = (MONITOR_CONTEXT *)v;     CODBCDatabase::InitCriticalSection();     db = new CODBCDatabase(Context->szParameters[0],         Context->szParameters[1],         Context->szParameters[2]);     if (db->isConnected())     {         result = 1;         wsprintf(Context->szReturnMsg,             "Connect to %s Succeeded!",             Context->szParameters[0]);         Context->fSuccess = true;     }     else     {         char diag[512];         char temp[512];         wsprintf(Context->szReturnMsg,             "Error connecting to %s! Last error was: %s",             Context->szParameters[0], db->getLastErrorString(diag));         // Quote quotes.         for (int loop = 0, skip = 0;             Context->szReturnMsg[loop] != '\0' ; loop++)         {             if (Context->szReturnMsg[loop] == '\'')             {                 temp[loop+skip] = '\'';                 skip++;             }             temp[loop+skip] = Context->szReturnMsg[loop];             temp[loop+skip+1] = '\0';         }         strcpy(Context->szReturnMsg, temp);         Context->fSuccess = false;     }     delete db;     CODBCDatabase::DelCriticalSection();     return result; } //------------------------------------------------------------------- // Tests SQL Server database, with enhancements based upon //  what we know about SQL Server int APIENTRY TestSQLServerStatus(PVOID v) {     MONITOR_CONTEXT *Context;     CODBCDatabase *db;     char szTemp[512];     int result = 0;     Context = (MONITOR_CONTEXT *)v;     CODBCDatabase::InitCriticalSection();     sprintf(szTemp, "DRIVER={SQL Server};Server=%s;"         "database=%s;uid=%s;pwd=%s",         Context->szParameters[0],         Context->szParameters[1],         Context->szParameters[2],         Context->szParameters[3]);     db = new CODBCDatabase(szTemp);     if (db->isConnected())     {         result = 1;         wsprintf(Context->szReturnMsg,             "Connect to %s Succeeded!",             Context->szParameters[0]);         Context->fSuccess = true;     }     else     {         char diag[512];         char temp[512];         MONITOR_CONTEXT ctx;         Context->fSuccess = false;         // Since we know the name of the server, try to ping it.         strcpy(ctx.szParameters[0], Context->szParameters[0]);         Ping(&ctx);         wsprintf(Context->szReturnMsg,             "Error connecting to %s/%s!  "             "Last error was: %s. Pinged and %s",             Context->szParameters[0],             Context->szParameters[1],             db->getLastErrorString(diag),             ctx.szReturnMsg);         // Quote quotes.         for (int loop = 0, skip = 0;             Context->szReturnMsg[loop] != '\0' ; loop++)         {             if (Context->szReturnMsg[loop] == '\'')             {                 temp[loop+skip] = '\'';                 skip++;             }             temp[loop+skip] = Context->szReturnMsg[loop];             temp[loop+skip+1] = '\0';         }         strcpy(Context->szReturnMsg, temp);         if (ctx.fSuccess)         {             ctx.fSuccess = false;             strcpy(ctx.szParameters[0], Context->szParameters[0]);             strcpy(ctx.szParameters[1], "MSSQLServer");             TestServiceStatus(&ctx);             if (ctx.fSuccess)             {                 strcat(Context->szReturnMsg,                     "  MSSQLServer Service is running.");             }             else             {                 strcat(Context->szReturnMsg,                     "  MSSQLServer Service is NOT running.");             }         }     }     delete db;     CODBCDatabase::DelCriticalSection();     return result; } 

Ping uses the standard TCP/IP method to open a raw socket to evoke an echo from a remote server. The Ping function as I implement it here is based upon the Ping.c sample included with the MSDN Platform SDK. I've made several adaptations. Most notably, there is no screen I/O associated with the operation of Ping . As with all DLL monitoring functions called by CMonitorService , all feedback is passed in the szReturnMsg parameter of the MONITOR_CONTEXT structure passed to the function. Additionally, I make only a single attempt to ping the server rather than multiple attempts.

At a high level, Ping opens a raw TCP/IP socket, finds the host based upon the name passed in szParameters[0] of the MONITOR_CONTEXT structure, and then sends a block of data and determines the amount of time the response took. Ping sets a timeout of 1 second. In real-world testing, connecting to local servers across a 100-Base-T network reports a send-receive interval between 0 and 14 milliseconds. Ping generally returns times of between 125 and 160 milliseconds over a RAS connection.

The details of implementing Ping are beyond the scope of this book. However, you will find it useful to understand the implications and use of Ping . Ping can provide additional information in the event of some other network failure. For example, in the event of a failure, TestSQLServerStatus  which I'll describe more fully in a moment uses its knowledge of the server name to call Ping to connect to the SQL Server database. The failed SQL Server connection combined with the results of a ping of the server can tell you a lot about the underlying cause of a reported problem.

TestServiceStatus is the second exported function in TestMonitor.dll. The purpose of TestServiceStatus is to determine whether a particular service is running on a given server. The server name and the service to check are passed in szParameters[0] and szParameters[1] , respectively. Since creating services is one of this book's major topics, TestServiceStatus deserves a more detailed explanation.

Recall that when we install a service, we first call the Win32 function OpenSCManager , which has the following prototype:

 SC_HANDLE OpenSCManager (    LPCTSTR lpMachineName,   // Pointer to machine name string     LPCTSTR lpDatabaseName,  // Pointer to database name string     DWORD dwDesiredAccess    // Type of access); 

The first parameter is the machine name. When we modify a service on the local machine, we can pass NULL for this parameter. If we want to check on the status of services running on another machine, lpMachineName must be the name of the other machine. NULL can be passed as the database name in lpDatabaseName because the default is currently the only allowed value. When all we need to do is check on existing services, we can pass SC_MANAGER_ENUMERATE_SERVICES as dwDesiredAccess .

Once OpenSCManager returns the handle to the SCM, we need to call EnumServicesStatus to check the status of services on the specified machine. The prototype for EnumServicesStatus is as follows:

 BOOL EnumServicesStatus(    SC_HANDLE hSCManager,    // Handle to service control manager                              //  database     DWORD dwServiceType,     // Type of services to enumerate     DWORD dwServiceState,    // State of services to enumerate     LPENUM_SERVICE_STATUS lpServices,                              // Pointer to service status buffer     DWORD cbBufSize,         // Size of service status buffer     LPDWORD pcbBytesNeeded,  // Pointer to variable for bytes needed     LPDWORD lpServicesReturned,                              // Pointer to variable for number                              //  returned     LPDWORD lpResumeHandle   // Pointer to variable for next entry); 

EnumServicesStatus does not return a single service's status, but rather reports on the status of all services. The first parameter is the SCM handle returned from OpenSCManager . The type of service to enumerate is passed in the second parameter, dwServiceType . This parameter can be SERVICE_WIN32 or SERVICE_DRIVER. In TestMonitor , this parameter is always SERVICE_WIN32. One complication of passing back the status of all services is the need to allocate a relatively large buffer to pass as lpServices . One alternative is to call the function first with a small buffer, as specified in cbBuffSize , and check the return code for an error. If GetLastError returns ERROR_MORE_DATA, pcbBytesNeeded will point to the number of bytes required to retrieve the remaining service entries.

The entries returned in lpServices are structures with an embedded structure. Both structures are shown here.

 typedef struct _ENUM_SERVICE_STATUS {     LPTSTR lpServiceName;     LPTSTR lpDisplayName;     SERVICE_STATUS ServiceStatus; } ENUM_SERVICE_STATUS; typedef struct _SERVICE_STATUS {     DWORD dwServiceType;     DWORD dwCurrentState;     DWORD dwControlsAccepted;     DWORD dwWin32ExitCode;     DWORD dwServiceSpecificExitCode;     DWORD dwCheckPoint;     DWORD dwWaitHint; } SERVICE_STATUS 

TestServiceStatus in TestMonitor uses only two elements. First I test the lpServiceName member of ENUM_SERVICE_STATUS to find the requested service. Once I've found the correct ENUM_SERVICE_STATUS structure, I check dwCurrentState in ServiceStatus . The possible values include SERVICE_STOPPED, SERVICE_STOP_PENDING, SERVICE_START_PENDING, SERVICE_PAUSED, and SERVICE_RUNNING. From the perspective of TestServiceStatus , any value for dwCurrentState except SERVICE_RUNNING is considered a failure. For most of the common status codes, I place a friendly representation of the service code in the szReturnMsg member of the MONITOR_CONTEXT structure passed to TestServiceStatus . This service code allows MonitorService to pass more useful information to the user. Appendix B discusses another way to use the Service Control Manager to monitor services running on Windows 2000 servers.

TestODBCConnection attempts to create a connection to an ODBC database by creating a CODBCDatabase object. It passes the ODBC data source name in szParameters[0] , and the user name and password in szParameters[1] and szParameters[2] , respectively. If the connection fails, a newly added GetLastError member of CODBCDatabase provides the SQLState and other descriptive information on any error that occurs during the attempt to connect. You'll find the modified ODBClass.cpp and ODBClass.h with the TestMonitor project on the companion CDROM.

There is one seemingly odd code fragment in TestODBCConnection .

 // Quote quotes. for (int loop = 0, skip = 0;     Context->szReturnMsg[loop] != '\0'; loop++) {     if (Context->szReturnMsg[loop] == '\'')     {         temp[loop+skip] = '\'';         skip++;     }     temp[loop+skip] = Context->szReturnMsg[loop];     temp[loop+skip+1] = '\0'; } strcpy(Context->szReturnMsg, temp); 

The purpose of this code is to replace each original single-quote character with two single-quote characters. Messages from the SQL Server ODBC driver commonly contain single quotes. It is important to duplicate single-quote characters because the string will eventually be passed to SQL Server as part of an insert operation. (SQL Server considers a single-quote character to be the end of a quoted string unless the single quote is followed immediately by another single quote.)

TestSQLServerStatus is the last of the four functions exported by TestMonitor. TestSQLServerStatus takes advantage of knowledge we have about SQL Server connections. Rather than using szParameters[0] as the ODBC data source name, szParameters[0] is the server name, szParameters[1] is the name of the database on the server, and szParameters[2] and szParameters[3] should hold the user name and password, respectively.

CODBCDatabase required a change to accommodate using SQLDriverConnect rather than SQLConnect to connect. SQLDriverConnect allows a connection that does not directly link to a predefined ODBC data source.

 SQLRETURN SQLDriverConnect(    SQLHDBC ConnectionHandle,     SQLHWND WindowHandle,     SQLCHAR *InConnectionString,     SQLSMALLINT StringLength1,     SQLCHAR *OutConnectionString,     SQLSMALLINT BufferLength,     SQLSMALLINT *StringLength2Ptr,     SQLUSMALLING DriverCompletion); 

Only a couple of the parameters to SQLDriverConnect are significant for this discussion. WindowHandle is passed as 0 in CODBCDatabase since our goal is to avoid any user interface. InConnectionString is a string that for SQL Server looks like this: "DRIVER={SQL Server};Server=<ServerName>;database=<DatabaseName>; -uid=<UserName>;password=<password>". In TestSQLServerStatus, SQL_DRIVER_NOPROMPT is sent in the DriverCompletion parameter to ensure that no dialogs are displayed.

If the connection fails, TestSQLServerStatus tests connectivity to the server using Ping . If Ping succeeds, I call TestServiceStatus passing the server name and "MSSQLServer" in szParameters[0] and szParameters[1] , respectively. I copy details of the success or failure of these additional tests to szReturnMsg to allow MonitorService to give the user more details of the failure.



Inside Server-Based Applications
Inside Server-Based Applications (DV-MPS General)
ISBN: 1572318171
EAN: 2147483647
Year: 1999
Pages: 91

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net