/*++ Copyright (C) Microsoft Corporation, 1997 - 1999 Module Name: lan.cxx Abstract: This is the source file relating to the LAN-specific routines of the Connectivity APIs implementation. Author: Gopal Parupudi [Notes:] optional-notes Revision History: GopalP 10/11/1997 Start. --*/ #include // // Constants // #define GETIFTABLE GetIfTable #define GETIPADDRTABLE GetIpAddrTable #define GETIPFORWARDTABLE GetIpForwardTable #define GETRTTANDHOPCOUNT GetRTTAndHopCount #define GETIPSTATISTICS GetIpStatistics #define MAX_IFTABLE_ROWS 4 #define MAX_IPADDRTABLE_ROWS 6 #define MAX_IPNETTABLE_ROWS 8 #define MAX_IPFORWARDTABLE_ROWS 8 #define MAX_HOPS_COUNT 0xFFFF #define BROADCAST_ACTIVITY_THRESHOLD 2 // +2 thru -2 #define MEDIASENSE_INITIALIZATION_DELAY 3*25*1000 // 1:15 minutes #define MEDIASENSE_EVALUATE_LAN_DELAY 4*1000 // 4 seconds #define SENS_WINSOCK_VERSION MAKEWORD( 2, 0 ) // // Globals // BOOL gbIpInitSuccessful; long gdwLastLANTime; long gdwLANState; IF_STATE gIfState[MAX_IF_ENTRIES]; MIB_IPSTATS gIpStats; extern CRITICAL_SECTION gSensLock; HANDLE ghMediaTimer; DWORD gdwMediaSenseState; // // Macros // /*++ Macro Description: A macro to help in allocating tables when calling IP Helper APIs. Arguments: TABLE_TYPE - The type of the IP Table being queried. ROW_TYPE - The type of the Row corresponding to the TABLE_TYPE. FUNC_NAME - The IP API to be called to get the IP table. MAX_NUM_ROWS - The default number of rows for the table which is being retrieved. These rows are allocated on the stack. Notes: o lpdwLastError should be defined as an LPDWORD in the code fragment that uses this macro. --*/ #define \ BEGIN_GETTABLE( \ TABLE_TYPE, \ ROW_TYPE, \ FUNC_NAME, \ MAX_NUM_ROWS \ ) \ { \ DWORD dwOldSize; \ DWORD dwSize; \ DWORD dwStatus; \ \ BOOL bOrder; \ \ TABLE_TYPE *pTable; \ \ bOrder = FALSE; \ \ dwSize = sizeof(DWORD) + MAX_NUM_ROWS * sizeof(ROW_TYPE); \ pTable = (TABLE_TYPE *) new char[dwSize]; \ if (pTable == NULL) \ { \ SensPrintA(SENS_MEM, (#FUNC_NAME "(): failed to new %d bytes\n", \ dwSize)); \ *lpdwLastError = ERROR_OUTOFMEMORY; \ return FALSE; \ } \ \ dwOldSize = dwSize; \ \ dwStatus = FUNC_NAME( \ pTable, \ &dwSize, \ bOrder \ ); \ \ if ( (dwStatus == ERROR_INSUFFICIENT_BUFFER) \ || (dwStatus == ERROR_MORE_DATA)) \ { \ ASSERT(dwSize > dwOldSize); \ SensPrintA(SENS_WARN, (#FUNC_NAME "(%d): reallocing buffer to be %d bytes\n", \ dwOldSize, dwSize)); \ delete (char *)pTable; \ pTable = (TABLE_TYPE *) new char[dwSize]; \ if (pTable != NULL) \ { \ dwStatus = FUNC_NAME( \ pTable, \ &dwSize, \ bOrder \ ); \ } \ else \ { \ SensPrintA(SENS_MEM, (#FUNC_NAME "(): failed to new (%d) bytes\n", \ dwSize)); \ *lpdwLastError = ERROR_OUTOFMEMORY; \ return FALSE; \ } \ } \ \ if (dwStatus != 0) \ { \ ASSERT( (dwStatus != ERROR_INSUFFICIENT_BUFFER) \ && (dwStatus != ERROR_MORE_DATA)); \ \ SensPrintA(SENS_ERR, (#FUNC_NAME "() returned %d\n", dwStatus));\ *lpdwLastError = dwStatus; \ /* P3 BUG: Might need to fire ConnectionLost() here */ \ gdwLANState = FALSE; \ UpdateSensCache(LAN); \ delete pTable; \ return FALSE; \ } /*++ Macro Description: This macro ends the BEGIN_GETTABLE() macro. Arguments: None. Notes: a. If we have a return between BEGIN_XXX and END_XXX, we need to make sure that we free pTable. --*/ #define \ END_GETTABLE() \ \ delete pTable; \ \ } BOOL DoLanSetup( void ) /*++ Routine Description: Arguments: None. Return Value: --*/ { BOOL bRetValue; WORD wVersionRequested; WSADATA wsaData; int err; bRetValue = FALSE; // // NT5-specific stuff // #if defined(SENS_NT5) ghMediaTimer = NULL; // Register for Media-sense notifications if (FALSE == MediaSenseRegister()) { SensPrintA(SENS_ERR, ("%s MediaSenseRegister() failed.\n", SERVICE_NAME)); } #endif // SENS_NT5 // // AOL-specific code // #if defined(AOL_PLATFORM) gdwAOLState = FALSE; #endif // AOL_PLATFORM // // Initialize Winsock. // wVersionRequested = SENS_WINSOCK_VERSION; err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { SensPrintA(SENS_ERR, ("WSAStartup() returned %d!\n", err)); bRetValue = FALSE; goto Cleanup; } bRetValue = TRUE; Cleanup: // // Cleanup // return bRetValue; } #ifdef DBG inline void PrintIfState( void ) /*++ Routine Description: Arguments: Return Value: --*/ { int i; SensPrintA(SENS_INFO, ("|---------------------------------------------------------------------------------------|\n")); SensPrintA(SENS_INFO, ("| Valid Index UcastIN UcastOUT NUcastIN NUcastOUT ErrIN ErrOUT DiscIN DiscOUT |\n")); SensPrintA(SENS_INFO, ("|---------------------------------------------------------------------------------------|\n")); for (i = 0; i < MAX_IF_ENTRIES; i++) { SensPrintA(SENS_INFO, ("| %c %9d %7d %7d %9d %9d %5d %6d %6d %6d |\n", gIfState[i].fValid ? 'Y' : 'N', gIfState[i].dwIndex, gIfState[i].dwInUcastPkts, gIfState[i].dwOutUcastPkts, gIfState[i].dwInNUcastPkts, gIfState[i].dwOutNUcastPkts, gIfState[i].dwInErrors, gIfState[i].dwOutErrors, gIfState[i].dwInDiscards, gIfState[i].dwOutDiscards) ); } SensPrintA(SENS_INFO, ("|---------------------------------------------------------------------------------------|\n")); } #else // DBG #define PrintIfState() // Nothing #endif // DBG #ifdef DETAIL_DEBUG void PrintIfTable( MIB_IFTABLE *pTable ) { int i; SensPrintA(SENS_INFO, ("|------------------------------------------------------------------------------|\n")); SensPrintA(SENS_INFO, ("| Type Index Spd/1K UcastIN UcastOUT ErrorIN OUT DiscIN OUT Opr Adm |\n")); SensPrintA(SENS_INFO, ("|------------------------------------------------------------------------------|\n")); for (i = 0; i < pTable->dwNumEntries; i++) { SensPrintA(SENS_INFO, ("| %4d %7d %6d %7d %8d %7d %3d %6d %3d %3d %3d |\n", pTable->table[i].dwType, pTable->table[i].dwIndex, pTable->table[i].dwSpeed/1000, pTable->table[i].dwInUcastPkts, pTable->table[i].dwOutUcastPkts, pTable->table[i].dwInErrors, pTable->table[i].dwOutErrors, pTable->table[i].dwInDiscards, pTable->table[i].dwOutDiscards, pTable->table[i].dwOperStatus, pTable->table[i].dwAdminStatus ) ); } SensPrintA(SENS_INFO, ("|------------------------------------------------------------------------------|\n")); } void PrintIpStats( void ) { SensPrintA(SENS_INFO, ("|------------------------------------|\n")); SensPrintA(SENS_INFO, ("| IP_STATS InReceives OutRequests |\n")); SensPrintA(SENS_INFO, ("|------------------------------------|\n")); SensPrintA(SENS_INFO, ("| %10d %10d |\n", gIpStats.dwInReceives, gIpStats.dwOutRequests) ); SensPrintA(SENS_INFO, ("|------------------------------------|\n")); } #else #define PrintIfTable(_X_) // Nothing #define PrintIpStats() // Nothing #endif // DETAIL_DEBUG BOOL WINAPI EvaluateLanConnectivityDelayed( LPDWORD ) { for (int i = 0; i < 4; i++) { Sleep(MEDIASENSE_EVALUATE_LAN_DELAY*i); // First time waits 0 ms, no delay if (EvaluateLanConnectivity(NULL)) { SensPrintA(SENS_INFO, ("EvaluateLanConnectivity: Delayed eval successful (%d)\n", i)); return TRUE; } } return FALSE; } BOOL WINAPI EvaluateLanConnectivity( OUT LPDWORD lpdwLastError ) /*++ Routine Description: Evaluates LAN Connectivity. Arguments: lpdwLastError - if return value is FALSE, GetLastError is returned in this OUT parameter. Notes: a. This routine can be entered by multiple threads at the same time. Currently only very essential code is under a critical section. b. This routine can be executed as a threadpool work item. Return Value: TRUE, if LAN connectivity is present. FALSE, otherwise --*/ { DWORD dwNow; DWORD dwLocalLastError; DWORD dwActiveInterfaceSpeed; WCHAR wszActiveInterfaceName[MAX_INTERFACE_NAME_LEN]; BOOL bLanAlive; BOOL bSomeInterfaceActive; BOOL bCheckCache; dwNow = GetTickCount(); dwLocalLastError = ERROR_NO_NETWORK; dwActiveInterfaceSpeed = 0x0; bLanAlive = FALSE; bSomeInterfaceActive = FALSE; bCheckCache = FALSE; if (lpdwLastError) { *lpdwLastError = dwLocalLastError; } else { lpdwLastError = &dwLocalLastError; } // // Get infomation about IP statistics. // BEGIN_GETTABLE(MIB_IFTABLE, MIB_IFROW, GETIFTABLE, MAX_IFTABLE_ROWS) // // PurgeStaleInterfaces // PurgeStaleInterfaces(pTable, lpdwLastError); // // Algorithm: // // o Create a record. // o See if this record exists. // o Save the record, if not and return success. // o If it does exist, compare. If greater, then save record. // o If not greater, try other entries. // o All entries? return failure. // IF_STATE ifEntry; DWORD i; SensPrintA(SENS_INFO, ("GetIfTable(): Number of entries - %d.\n", pTable->dwNumEntries)); PrintIfTable(pTable); i = 0; while (i < pTable->dwNumEntries) { // // Calculate only if it is a non-WAN and non-Loopback interface. // if ( (pTable->table[i].dwType != MIB_IF_TYPE_PPP) && (pTable->table[i].dwType != MIB_IF_TYPE_SLIP) && (pTable->table[i].dwType != MIB_IF_TYPE_LOOPBACK)) { BOOL bForceInvalid = FALSE; // // BOOT UP WITH NO NETWORK: // // Check to see if both UnicastIN and UnicastOUT are zero. If so, // this interface is considered as not active and we skip it. // if ( (pTable->table[i].dwInUcastPkts == 0) && (pTable->table[i].dwOutUcastPkts == 0)) { bForceInvalid = TRUE; } // // Check if networking says it is connected, if not, skip it. // if (pTable->table[i].dwOperStatus < MIB_IF_OPER_STATUS_CONNECTING) { SensPrintA(SENS_INFO, ("GetIfTable: Found interface %d in < connecting state (%d), ignored\n", pTable->table[i].dwIndex, pTable->table[i].dwOperStatus) ); bForceInvalid = TRUE; } // // At this stage, there is some Unicast activity on this interface. // So, we can skip the check for Unicast activity below. // // Fill the IF_STATE structure ifEntry.dwIndex = pTable->table[i].dwIndex; ifEntry.dwInUcastPkts = pTable->table[i].dwInUcastPkts; ifEntry.dwOutUcastPkts = pTable->table[i].dwOutUcastPkts; ifEntry.dwInNUcastPkts = pTable->table[i].dwInNUcastPkts; ifEntry.dwOutNUcastPkts = pTable->table[i].dwOutNUcastPkts; ifEntry.dwInErrors = pTable->table[i].dwInErrors; ifEntry.dwOutErrors = pTable->table[i].dwOutErrors; ifEntry.dwInDiscards = pTable->table[i].dwInDiscards; ifEntry.dwOutDiscards = pTable->table[i].dwOutDiscards; bSomeInterfaceActive = HasIfStateChanged(ifEntry, bForceInvalid); if (TRUE == bSomeInterfaceActive) { bLanAlive = TRUE; // Save info about interface for later use. dwActiveInterfaceSpeed = max(pTable->table[i].dwSpeed,dwActiveInterfaceSpeed); StringCchCopy(wszActiveInterfaceName, MAX_INTERFACE_NAME_LEN, pTable->table[i].wszName); } else { if (!bForceInvalid) { bCheckCache = TRUE; // Idle IF found but still valid (enable MAX_LAN_INTERNAL check below) } } } i++; } // while () PrintIfState(); END_GETTABLE() // // RACE Condition Fix: // // If there are 2 threads that are in EvaluateLanConnectivity() and one // of them updates the interface's packet cache, then there is a distinct // possibility that the second thread will compare with the updated cache // and wrongly conclude that there is no activity. We ignore any loss of // connectivity that was evaluated before MAX_LAN_INTERVAL (ie., we should // keep giving cached information for MAX_LAN_INTERVAL seconds). // if ( (TRUE == bCheckCache) && (FALSE == bLanAlive) ) { dwNow = GetTickCount(); if ( ((dwNow - gdwLastLANTime) <= MAX_LAN_INTERVAL) && (gdwLastLANTime != 0) ) { SensPrintA(SENS_DBG, ("EvaluateLanConnectivity(): Returning TRUE " "(Now - %d sec, LastLANTime - %d sec)\n", dwNow/1000, gdwLastLANTime/1000)); return TRUE; } } // // NOTE: If we are doing DWORD InterlockedExchange, then assignment // should suffice. Using InterlockedExchange is not a bug, though. // if (bLanAlive) { SensPrintA(SENS_DBG, ("**** EvaluateLanConnectivity(): Setting" " gdwLastLANTime to %d secs\n", dwNow/1000)); InterlockedExchange(&gdwLastLANTime, dwNow); } else { SensPrintA(SENS_DBG, ("**** EvaluateLanConnectivity(): Setting" " gdwLastLANTime to 0 secs\n")); InterlockedExchange(&gdwLastLANTime, 0x0); } // // Adjust LAN state and fire an event, if necessary. // if (InterlockedExchange(&gdwLANState, bLanAlive) != bLanAlive) { // // LAN Connectivity state changed. // SENSEVENT_NETALIVE Data; Data.eType = SENS_EVENT_NETALIVE; Data.bAlive = bLanAlive; memset(&Data.QocInfo, 0x0, sizeof(QOCINFO)); Data.QocInfo.dwSize = sizeof(QOCINFO); Data.QocInfo.dwFlags = NETWORK_ALIVE_LAN; Data.QocInfo.dwInSpeed = dwActiveInterfaceSpeed; Data.QocInfo.dwOutSpeed = dwActiveInterfaceSpeed; // // NOTE: When dwActiveInterfaceName gets the right value from // IPHLPAPIs we should use that name. Until then, we use a default. // Data.strConnection = DEFAULT_LAN_CONNECTION_NAME; UpdateSensCache(LAN); SensFireEvent((PVOID)&Data); } return bLanAlive; } BOOL HasIfStateChanged( IF_STATE ifEntry, BOOL bForceInvalid ) /*++ Routine Description: Compares the current state of a remote network IF with the cached history to determine if it is active or not. Arguments: ifEntry - An interface that appears to have changed state and is "valid" as a remote LAN if. (ie, loopback, pptp, etc should be filtered out) bForceInvalid - If TRUE, don't bother to look at the stats; this interface is NOT valid. Return Value: TRUE - ifEntry appears up and active FALSE - ifEntry inactive or down --*/ { int i, j; static int iLastActiveIndex = -1; BOOL bActive; BOOL bSeenButInactive; DWORD dwInDiff; DWORD dwOutDiff; int iNUcastDiff; i = 0; bActive = FALSE; bSeenButInactive = FALSE; dwInDiff = 0; dwOutDiff = 0; iNUcastDiff = 0; RequestSensLock(); // // Compare the current snapshot with the saved snapshot // for this interface. // while (i < MAX_IF_ENTRIES) { if ( (gIfState[i].fValid == TRUE) && (gIfState[i].dwIndex == ifEntry.dwIndex)) { if (bForceInvalid) { gIfState[i].fValid = FALSE; break; } if ( (ifEntry.dwInUcastPkts > gIfState[i].dwInUcastPkts) || (ifEntry.dwOutUcastPkts > gIfState[i].dwOutUcastPkts) || (ifEntry.dwInNUcastPkts > gIfState[i].dwInNUcastPkts) || (ifEntry.dwOutNUcastPkts > gIfState[i].dwOutNUcastPkts) || (ifEntry.dwInErrors > gIfState[i].dwInErrors) || (ifEntry.dwOutErrors > gIfState[i].dwOutErrors) || (ifEntry.dwInDiscards > gIfState[i].dwInDiscards) || (ifEntry.dwOutDiscards > gIfState[i].dwOutDiscards)) { // // HEURISTIC: // // a. When the net tap is pulled out, it has been observed that // the difference in the incoming non-Unicast packet count // is within +1 thru -1 of the difference in the outgoing // non-Unicast packet count. Most of the times the diff of // these differences is 0. We don't count this as LAN alive // // b. Also, there should be no change in the unicast IN packet // count. Unicast OUT packet count may change. This could be // problematic. // dwInDiff = ifEntry.dwInNUcastPkts - gIfState[i].dwInNUcastPkts; dwOutDiff = ifEntry.dwOutNUcastPkts - gIfState[i].dwOutNUcastPkts; iNUcastDiff = dwOutDiff - dwInDiff; SensPrintA(SENS_INFO, ("HasIfStateChanged(): dwInDiff = %d, " "dwOutDiff = %d, dwNUcastDiff = %d, UcastINDiff = " "%d, UcastOUTDiff = %d\n", dwInDiff, dwOutDiff, iNUcastDiff, ifEntry.dwInUcastPkts - gIfState[i].dwInUcastPkts, ifEntry.dwOutUcastPkts - gIfState[i].dwOutUcastPkts)); if ( (ifEntry.dwInUcastPkts == gIfState[i].dwInUcastPkts) && (iNUcastDiff <= BROADCAST_ACTIVITY_THRESHOLD) && (iNUcastDiff >= -BROADCAST_ACTIVITY_THRESHOLD)) { SensPrintA(SENS_INFO, ("HasIfStateChanged(): Interface %d" " has only Broadcast activity (Diff is %d)!\n", gIfState[i].dwIndex, iNUcastDiff)); bSeenButInactive = TRUE; } else { // // Unicast IN packet counts have changed or Broadcast // activity is greater than the threshold. // iLastActiveIndex = i; bActive = TRUE; SensPrintA(SENS_INFO, ("HasStateChanged(): Interface %d " "has been active.\n", gIfState[i].dwIndex)); gdwLastLANTime = GetTickCount(); SensPrintA(SENS_DBG, ("**** HasIfStateChanged(): Setting " "gdwLastLANTime to %d secs\n", gdwLastLANTime/1000)); } // // Save the new values. // memcpy(&gIfState[i], &ifEntry, sizeof(IF_STATE)); gIfState[i].fValid = TRUE; } else { SensPrintA(SENS_INFO, ("HasStateChanged(): Interface %d has NO activity.\n", gIfState[i].dwIndex)); bSeenButInactive = TRUE; } // Found the interface, so stop searching break; } i++; } // while () ReleaseSensLock(); if ( (bSeenButInactive == TRUE) || (bForceInvalid) ) { return FALSE; } if (bActive == TRUE) { return TRUE; } // // We are seeing this interface for the first time. Go ahead and save it // in the global interface state array. // i = MAX_IF_ENTRIES; j = iLastActiveIndex; RequestSensLock(); while (i > 0) { // Try to find a free slot starting from the last active slot. j = (j+1) % MAX_IF_ENTRIES; if (gIfState[j].fValid == FALSE) { // Found one! break; } i--; } // // NOTE: If there are more than MAX_IF_ENTRIES, we will start // start reusing valid interface elements in gIfState array. This, // I guess, is OK since we will have enough interfaces to figure // out connectivity. // memcpy(&gIfState[j], &ifEntry, sizeof(IF_STATE)); gIfState[j].fValid = TRUE; ReleaseSensLock(); SensPrintA(SENS_ERR, ("******** HasIfStateChanged(): Adding a new " "interface with index %d\n", gIfState[j].dwIndex)); return TRUE; } BOOL MediaSenseRegister( void ) /*++ Routine Description: Schedule a workitem to register for Media-sense notifications from WMI. Arguments: None. Return Value: TRUE, if success. FALSE, otherwise. --*/ { BOOL bRetVal; bRetVal = TRUE; ASSERT(gdwMediaSenseState == SENSSVC_START); // // Create a timer object to schedule (one-time only) Media-sense // registration. // SensSetTimerQueueTimer( bRetVal, // Bool return on NT5 ghMediaTimer, // Handle return on IE5 NULL, // Use default process timer queue MediaSenseRegisterHelper, // Callback NULL, // Parameter MEDIASENSE_INITIALIZATION_DELAY, // Time from now when timer should fire 0x0, // Time inbetween firings of this timer 0x0 // No Flags. ); if (SENS_TIMER_CREATE_FAILED(bRetVal, ghMediaTimer)) { SensPrintA(SENS_ERR, ("MediaSenseRegister(): SensSetTimerQueueTimer() failed with %d.\n", GetLastError())); bRetVal = FALSE; } return bRetVal; } SENS_TIMER_CALLBACK_RETURN MediaSenseRegisterHelper( PVOID pvIgnore, BOOLEAN bIgnore ) /*++ Routine Description: Helper routine that is scheduled to the WMI registration. Arguments: pvIgnore - Ignored. bIgnore - Ignored. Return Value: None (void). --*/ { ULONG Status; GUID guid; RequestSensLock(); if ( (SENSSVC_STOP == gdwMediaSenseState) || (UNREGISTERED == gdwMediaSenseState)) { goto Cleanup; } // // Enable the media disconnect event. // guid = GUID_NDIS_STATUS_MEDIA_DISCONNECT; Status = WmiNotificationRegistrationW( &guid, // Event of interest TRUE, // Enable Notification? EventCallbackRoutine, // Callback function 0, // Callback context NOTIFICATION_CALLBACK_DIRECT // Notification flags ); if (ERROR_SUCCESS != Status) { SensPrintA(SENS_ERR, ("Unable to enable media disconnect event: 0x%x!\n", Status)); goto Cleanup; } // // Enable the media connect event // guid = GUID_NDIS_STATUS_MEDIA_CONNECT; Status = WmiNotificationRegistrationW( &guid, // Event of interest TRUE, // Enable Notification? EventCallbackRoutine, // Callback function 0, // Callback context NOTIFICATION_CALLBACK_DIRECT // Notification flags ); if (ERROR_SUCCESS != Status) { SensPrintA(SENS_ERR, ("Unable to enable media connect event: 0x%x!\n", Status)); ASSERT(0); // If we hit this then we need to unregister the first registration above. goto Cleanup; } SensPrintA(SENS_ERR, ("MediaSenseRegister(): Media-sense registration successful.\n")); gdwMediaSenseState = REGISTERED; Cleanup: // // Cleanup // ReleaseSensLock(); return; } BOOL MediaSenseUnregister( void ) /*++ Routine Description: Unregister from Media-sense notifications from WMI. Arguments: None. Return Value: TRUE, if success. FALSE, otherwise. --*/ { ULONG Status; GUID guid; BOOL bRetVal; BOOL bRegistered; bRetVal = TRUE; bRegistered = FALSE; RequestSensLock(); ASSERT(gdwMediaSenseState == REGISTERED || gdwMediaSenseState == SENSSVC_START); if (gdwMediaSenseState == REGISTERED) { bRegistered = TRUE; } gdwMediaSenseState = SENSSVC_STOP; if (NULL != ghMediaTimer) { bRetVal = SensCancelTimerQueueTimer(NULL, ghMediaTimer, NULL); ghMediaTimer = NULL; SensPrintA(SENS_INFO, ("[%d] MediaSensUnregister(): SensCancelTimer" "QueueTimer(Media) %s\n", GetTickCount(), bRetVal ? "succeeded" : "failed!")); } if (!bRegistered) { // Should not do unregistration. goto Cleanup; } // // Disable the media disconnect event. // guid = GUID_NDIS_STATUS_MEDIA_DISCONNECT; Status = WmiNotificationRegistrationW( &guid, // Event of interest FALSE, // Enable Notification? EventCallbackRoutine, // Callback function 0, // Callback context NOTIFICATION_CALLBACK_DIRECT // Notification flags ); if (ERROR_SUCCESS != Status) { SensPrintA(SENS_ERR, ("[%d] MediaSensUnregister(): Unable to disable " "media disconnect event: 0x%x!\n", GetTickCount(), Status)); ASSERT(0); // If this fails analyze if we should still to the second unregister bRetVal = FALSE; } // // Disable the connect event // guid = GUID_NDIS_STATUS_MEDIA_CONNECT; Status = WmiNotificationRegistrationW( &guid, // Event of interest FALSE, // Enable Notification? EventCallbackRoutine, // Callback function 0, // Callback context NOTIFICATION_CALLBACK_DIRECT // Notification flags ); if (ERROR_SUCCESS != Status) { SensPrintA(SENS_ERR, ("[%d] MediaSensUnregister(): Unable to disable " "media disconnect event: 0x%x!\n", GetTickCount(), Status)); bRetVal = FALSE; } Cleanup: // // // gdwMediaSenseState = UNREGISTERED; ReleaseSensLock(); return bRetVal; } void EventCallbackRoutine( IN PWNODE_HEADER WnodeHeader, IN ULONG Context ) /*++ Routine Description: Arguments: Return Value: --*/ { PULONG Data; PWNODE_SINGLE_INSTANCE Wnode = (PWNODE_SINGLE_INSTANCE)WnodeHeader; PWCHAR Name; DWORD dwIgnore; ULONG NameLen; int result; // // Get the information for the media disconnect. // result = memcmp(&WnodeHeader->Guid, &GUID_NDIS_STATUS_MEDIA_DISCONNECT, sizeof(GUID)); if (0 == result) { SensPrintA(SENS_INFO, ("NDIS: received a media disconnect!\n")); EvaluateConnectivity(TYPE_LAN); } else { // // Get the information for the media connect. // result = memcmp(&WnodeHeader->Guid, &GUID_NDIS_STATUS_MEDIA_CONNECT, sizeof(GUID)); if (0 == result) { SensPrintA(SENS_INFO, ("NDIS: received a media connect!\n")); EvaluateConnectivity(TYPE_DELAY_LAN); } else { SensPrintA(SENS_WARN, ("NDIS: Unknown event received!\n")); } } Name = (PWCHAR)RtlOffsetToPointer(Wnode, Wnode->OffsetInstanceName); SensPrintW(SENS_INFO, (L"NDIS: Instance: %ws\n", Name)); } BOOL GetIfEntryStats( IN DWORD dwIfIndex, IN LPQOCINFO lpQOCInfo, OUT LPDWORD lpdwLastError, OUT LPBOOL lpbIsWanIf ) /*++ Routine Description: Get the Statistics field of the Interface entry which has the given index. Arguments: dwIfIndex - The interface of interest. lpQOCInfo - QOC Info structure whose fields are set when the interface entry is found in the interface table. lpdwLastError - The GLE, if any. lpbIsWanIf - Is the interface at this index a WAN interface or not. Return Value: TRUE, if we find the index. FALSE, otherwise. --*/ { DWORD i; BOOL bFound; *lpdwLastError = ERROR_SUCCESS; *lpbIsWanIf = FALSE; bFound = FALSE; BEGIN_GETTABLE(MIB_IFTABLE, MIB_IFROW, GETIFTABLE, MAX_IFTABLE_ROWS) // Search the Interface table for the entry with the given index. for (i = 0; i < pTable->dwNumEntries; i++) { if (pTable->table[i].dwIndex == dwIfIndex) { bFound = TRUE; SensPrintA(SENS_INFO, ("GetIfEntryStats(): Interface %d is of " "type %d\n", dwIfIndex, pTable->table[i].dwType)); if ( (pTable->table[i].dwType == MIB_IF_TYPE_PPP) || (pTable->table[i].dwType == MIB_IF_TYPE_SLIP)) { *lpbIsWanIf = TRUE; } else { *lpbIsWanIf = FALSE; } if (lpQOCInfo != NULL) { lpQOCInfo->dwSize = sizeof(QOCINFO); lpQOCInfo->dwInSpeed = pTable->table[i].dwSpeed; lpQOCInfo->dwOutSpeed = pTable->table[i].dwSpeed; lpQOCInfo->dwFlags = (*lpbIsWanIf) ? CONNECTION_WAN : CONNECTION_LAN; } break; } } END_GETTABLE() return bFound; } BOOL CheckForReachability( IN IPAddr DestIpAddr, IN OUT LPQOCINFO lpQOCInfo, OUT LPDWORD lpdwLastError ) /*++ Routine Description: This helper function does all the dirty work in checking for Reachability of a particular destination. Arguments: DestIpAddr - The Destination of interest. lpQOCInfo - The QOC Info structure. lpdwLastError - Returns the GetLastError value when the destination is not reachable. Return Value: TRUE, if the destination IP Address is reachable FALSE, otherwise. GLE returned in lpdwLastError. --*/ { DWORD i; BOOL bSuccess; BOOL bSameNetId; BOOL bReachable; BOOL bIsWanIf; DWORD dwNetId; DWORD dwSubnetMask; DWORD ifNum; DWORD dwHopCount; DWORD dwRtt; ifNum = -1; dwRtt = 0; bSuccess = FALSE; bIsWanIf = FALSE; bReachable = FALSE; bSameNetId = FALSE; // // Search the IP Address table for an entry with the same NetId as the // Destination. If such an entry exists, the Destination is in the same // sub-net and hence reachable. // BEGIN_GETTABLE(MIB_IPADDRTABLE, MIB_IPADDRROW, GETIPADDRTABLE, MAX_IPADDRTABLE_ROWS) // Search for an entry with the same NetId for (i = 0; i < pTable->dwNumEntries; i++) { // Compare NetIds. dwSubnetMask = pTable->table[i].dwMask; dwNetId = pTable->table[i].dwAddr & dwSubnetMask; SensPrintA(SENS_INFO, ("IPADDRESS(%d) - Mask %8x, IP %8x, NETID %8x, COMP %8x\n", i, pTable->table[i].dwMask, pTable->table[i].dwAddr, dwNetId, (DestIpAddr & dwSubnetMask)) ); if ( (pTable->table[i].dwAddr != 0x0) && ((DestIpAddr & dwSubnetMask) == dwNetId)) { bSameNetId = TRUE; ifNum = pTable->table[i].dwIndex; SensPrintA(SENS_INFO, ("CheckForReachability(): Found entry in IPAddr Table with same NetId\n")); break; } } END_GETTABLE() if (bSameNetId) { // Destination is in the same Subnet. Get stats from the IfTable. bSuccess = GetIfEntryStats(ifNum, lpQOCInfo, lpdwLastError, &bIsWanIf); ASSERT(bSuccess == TRUE); if (bSuccess) { return TRUE; } } // // Entry is not in the IP AddrTable. We need to Ping. Search the Gateway // table for default gateway and get it's interface statistics. // BEGIN_GETTABLE(MIB_IPFORWARDTABLE, MIB_IPFORWARDROW, GETIPFORWARDTABLE, MAX_IPFORWARDTABLE_ROWS) for (i = 0; i < pTable->dwNumEntries; i++) { dwSubnetMask = pTable->table[i].dwForwardMask; dwNetId = pTable->table[i].dwForwardDest & dwSubnetMask; ifNum = pTable->table[i].dwForwardIfIndex; SensPrintA(SENS_INFO, ("IPFORWARD(%d) - Mask %8x, IP %8x, NETID %8x, COMP %8x\n", i, pTable->table[i].dwForwardMask, pTable->table[i].dwForwardDest, dwNetId, (DestIpAddr & dwSubnetMask)) ); if (pTable->table[i].dwForwardDest == 0x0) { // // Skip the default gateway 0.0.0.0. But, get the statistics // anyways. The QOC of the default gateway is used if we have // to Ping the destination. // bSuccess = GetIfEntryStats(ifNum, lpQOCInfo, lpdwLastError, &bIsWanIf); SensPrintA(SENS_INFO, ("Default Gateway statistics (if = %d, " "dwSpeed = %d, IsWanIf = %s)\n", ifNum, lpQOCInfo ? lpQOCInfo->dwInSpeed : 0x0, bIsWanIf ? "TRUE" : "FALSE")); ASSERT(bSuccess == TRUE); break; } } END_GETTABLE() // // Resort to a Ping // bReachable = GETRTTANDHOPCOUNT( DestIpAddr, &dwHopCount, MAX_HOPS_COUNT, &dwRtt ); // // If we got around to doing a Ping, QOC information will have been // retrieved when we found the Default Gateway entry. // SensPrintA(SENS_INFO, ("CheckForReachability(): Ping returned %s with GLE of %d\n", bReachable ? "TRUE" : "FALSE", GetLastError())); if (bReachable == FALSE) { *lpdwLastError = ERROR_HOST_UNREACHABLE; } // // P3 BUG: // // a. We determine whether the interface on which the Ping went is a LAN // or WAN by checking the interface type of the default gateway. This // is not TRUE! // return bReachable; } BOOL GetActiveWanInterfaceStatistics( OUT LPDWORD lpdwLastError, OUT LPDWORD lpdwWanSpeed ) /*++ Routine Description: Get the Statistics field of the Interface entry Arguments: lpdwLastError - The GLE, if any. lpdwWanSpeed - Speed of the WAN interface Notes: P3 BUG: Currently, this will return the speed of the first WAN interface it finds. This won't work properly if there are multiple "active" WAN interfaces. Return Value: TRUE, if statistics were successfully retrieved FALSE, otherwise. --*/ { DWORD i; BOOL bFound; *lpdwLastError = ERROR_SUCCESS; *lpdwWanSpeed = DEFAULT_WAN_BANDWIDTH; bFound = FALSE; BEGIN_GETTABLE(MIB_IFTABLE, MIB_IFROW, GETIFTABLE, MAX_IFTABLE_ROWS) // Search the Interface table for the first active WAN interface. for (i = 0; i < pTable->dwNumEntries; i++) { if ( (pTable->table[i].dwType == MIB_IF_TYPE_PPP) || (pTable->table[i].dwType == MIB_IF_TYPE_SLIP)) { bFound = TRUE; if ( (pTable->table[i].dwInNUcastPkts != 0) || (pTable->table[i].dwOutNUcastPkts != 0) || (pTable->table[i].dwInErrors != 0) || (pTable->table[i].dwOutErrors != 0) || (pTable->table[i].dwInDiscards != 0) || (pTable->table[i].dwOutDiscards != 0)) { *lpdwWanSpeed = pTable->table[i].dwSpeed; break; } } } // for END_GETTABLE() return bFound; } BOOL PurgeStaleInterfaces( IN MIB_IFTABLE *pTable, OUT LPDWORD lpdwLastError ) /*++ Routine Description: Remove statistics from the interfaces that went away. Arguments: pTable - The current If table. lpdwLastError - The GLE, if any. Return Value: TRUE, always. --*/ { DWORD i; DWORD j; BOOL bFound; *lpdwLastError = ERROR_SUCCESS; RequestSensLock(); // Check if each valid interface in the cache still exists. for (j = 0; j < MAX_IF_ENTRIES; j++) { if (gIfState[j].fValid == FALSE) { continue; } bFound = FALSE; // Search if the interface in the cache is present in the IF_TABLE. for (i = 0; i < pTable->dwNumEntries; i++) { if (pTable->table[i].dwIndex == gIfState[j].dwIndex) { bFound = TRUE; } } // for (i) if (FALSE == bFound) { SensPrintA(SENS_ERR, ("******** PurgeStaleInterfaces(): Purging" "interface with index %d\n", gIfState[j].dwIndex)); // Interface went away. So remove from Cache. memset(&gIfState[j], 0x0, sizeof(IF_STATE)); gIfState[j].fValid = FALSE; } } // for (j) ReleaseSensLock(); return TRUE; }