/************************************************************************ Copyright (c) 2000 - 2000 Microsoft Corporation Module Name : netspeed.cpp Abstract : Main source file for throttle control. Author : Revision History : ---> for small files, we need to feed the file size in to the block calculator, because the server-speed estimator will be incorrect if m_BlockSize is 65000 but the download time is based on a file size of 2002 bytes. ***********************************************************************/ #include "stdafx.h" #include #include #if !defined(BITS_V12_ON_NT4) #include "netspeed.tmh" #endif // // the maximum % of the perceived bandwidth that BITS will use for itself // const float MAX_BANDWIDTH_FRACTION = 0.95f; // // timer periods in seconds // const float DEFAULT_BLOCK_INTERVAL = 2.0f; const float MIN_BLOCK_INTERVAL = 0.001f; const float MAX_BLOCK_INTERVAL = 5.0f; // // observed header sizes. request = 250, reply = 300 // #define REQUEST_OVERHEAD 550 // // smallest block we will pull down // #define MIN_BLOCK_SIZE 2000 // // size when we occasionally pull down a block on a full network // #define BUSY_BLOCK_SIZE 1500 // // Very small blocks give unreliable speed measurements. // #define MIN_BLOCK_SIZE_TO_MEASURE 500 const NETWORK_RATE CNetworkInterface::DEFAULT_SPEED = 1600.0f; // Work around limitations of the protocol stack const DWORD MAX_BLOCK_SIZE = 2147483648; //------------------------------------------------------------------------ // // The observed server speed is reported as the average of this many usable samples. // const float SERVER_SPEED_SAMPLE_COUNT = 3.0F; /* The algorithm used to determine the speed and loading of the network is as follows: 1. After contacting the web site with Wininet calls, see whether an HTTP 1.1 "Via" header is present. If so, a proxy was used, and we locate the proper net card to talk with the proxy. Otherwise, a proxy was not used, and we locate the proper net card to talk with the HTTP server itself. 2. Chop time into 1/2-second intervals, and measure the interface's bytes-in and bytes-out count three times per interval: first at the beginning, just before a block is downloaded, second at the completion of the block, and third at the end of the interval. */ HRESULT CNetworkInterface::TakeSnapshot( int StatIndex ) { DWORD s; ULONG size = 0; // // The network speed can be calculated only if all three snapshots succeeded. // We keep track of the error status of the current series of snapshots. // if (StatIndex == BLOCK_START) { m_SnapshotError = S_OK; m_SnapshotsValid = false; } m_TempRow.dwIndex = m_InterfaceIndex; DWORD dwGetIfEntryError = GetIfEntry( &m_TempRow ); if ( dwGetIfEntryError ) { LogWarning( "[%d] : GetIfRow(%d) failed %!winerr!", StatIndex, m_InterfaceIndex, dwGetIfEntryError ); m_SnapshotError = HRESULT_FROM_WIN32( dwGetIfEntryError ); return m_SnapshotError; } QueryPerformanceCounter( &m_Snapshots[ StatIndex ].TimeStamp ); m_Snapshots[ StatIndex ].BytesIn = m_TempRow.dwInOctets; m_Snapshots[ StatIndex ].BytesOut = m_TempRow.dwOutOctets; LogDl( "[%d] : in=%d, out=%d, timestamp=%d", StatIndex, m_Snapshots[ StatIndex ].BytesIn, m_Snapshots[ StatIndex ].BytesOut, m_Snapshots[ StatIndex ].TimeStamp.u.LowPart ); if (StatIndex == BLOCK_INTERVAL_END && m_SnapshotError == S_OK) { m_SnapshotsValid = true; } return S_OK; } float CNetworkInterface::GetTimeDifference( int start, int finish ) { float TotalTime; TotalTime = m_Snapshots[ finish ].TimeStamp.QuadPart - m_Snapshots[ start ].TimeStamp.QuadPart; TotalTime /= g_GlobalInfo->m_PerformanceCounterFrequency.QuadPart; // convert to seconds if (TotalTime <= 0) { // pretend it was half a tick. TotalTime = 1 / float(2 * g_GlobalInfo->m_PerformanceCounterFrequency.QuadPart); } return TotalTime; } CNetworkInterface::CNetworkInterface() { Reset(); } HRESULT CNetworkInterface::SetInterfaceIndex( const TCHAR host[] ) { DWORD index; HRESULT Hr = FindInterfaceIndex( host, &index ); if (FAILED(Hr)) return Hr; if (m_InterfaceIndex != index) { m_InterfaceIndex = index; Reset(); } return S_OK; } void CNetworkInterface::Reset() { m_ServerSpeed = DEFAULT_SPEED; m_NetcardSpeed = DEFAULT_SPEED; m_PercentFree = 0.5f; m_SnapshotsValid = false; m_SnapshotError = E_FAIL; m_state = DOWNLOADED_BLOCK; } void CNetworkInterface::SetInterfaceSpeed() { float TotalTime, ratio; NETWORK_RATE rate = 0; // // Adjust server speed based on block download stats. // if (m_SnapshotsValid && m_BlockSize) { float ExpectedTime = m_BlockInterval * m_PercentFree; // // Calculate interface speed from the time the last block took. // TotalTime = GetTimeDifference( BLOCK_START, BLOCK_END ); if (ExpectedTime > 0) { ratio = ExpectedTime / TotalTime; rate = m_ServerSpeed * ratio; } else { // either m_PercentFree was zero, or the interval was zero. The ordinary calculation // would always produce a ratio of zero and drag down our average speed incorrectly. // use strict bytes per second measure rate = m_BlockSize / TotalTime; if (rate < m_ServerSpeed) { rate = m_ServerSpeed; } } m_ServerSpeed *= (SERVER_SPEED_SAMPLE_COUNT-1) / SERVER_SPEED_SAMPLE_COUNT; m_ServerSpeed += (rate / SERVER_SPEED_SAMPLE_COUNT); LogDl("expected interval %f, actual= %f, rate= %!netrate!, avg %!netrate!", ExpectedTime, TotalTime, rate, m_ServerSpeed ); } // // Adjust usage and netcard speed based on interval stats. // if (m_SnapshotsValid) { float Bytes; Bytes = m_Snapshots[ BLOCK_END ].BytesIn - m_Snapshots[ BLOCK_START ].BytesIn; Bytes += m_Snapshots[ BLOCK_END ].BytesOut - m_Snapshots[ BLOCK_START ].BytesOut; ASSERT( Bytes >= 0 ); TotalTime = GetTimeDifference( BLOCK_START, BLOCK_END ); rate = Bytes/TotalTime; // use whichever estimate is larger if (rate < m_ServerSpeed) { rate = m_ServerSpeed; } if (m_NetcardSpeed == 0) { m_NetcardSpeed = rate; } else { if (rate < m_NetcardSpeed * 0.9f) { // // If the rate drops precipitously, it's probably just a quiet moment on the Net; // a strict average would unduly lower our estimated throughput. // But reduce the average a little in case it's a long-term slowdown. If so, // eventually the average will be lowered enough that the incoming rates are greater // than m_NetcardSpeed / 2. // rate = m_NetcardSpeed * 0.9f; } // // Keep a running average of the perceived rate. // m_NetcardSpeed *= (SERVER_SPEED_SAMPLE_COUNT-1) / SERVER_SPEED_SAMPLE_COUNT; m_NetcardSpeed += (rate / SERVER_SPEED_SAMPLE_COUNT); } LogDl("bandwidth: bytes %f, time %f, rate %f, avg. %f", Bytes, TotalTime, rate, m_NetcardSpeed); // // Subtract our usage from the calculated usage. Compare usage to top speed to get free bandwidth. // Bytes = m_Snapshots[ BLOCK_INTERVAL_END ].BytesIn - m_Snapshots[ BLOCK_START ].BytesIn; Bytes += m_Snapshots[ BLOCK_INTERVAL_END ].BytesOut - m_Snapshots[ BLOCK_START ].BytesOut; Bytes -= m_BlockSize; if (Bytes < 0) { Bytes = 0; } TotalTime = GetTimeDifference( BLOCK_START, BLOCK_INTERVAL_END ); rate = Bytes/TotalTime; m_PercentFree = 1 - (rate / m_NetcardSpeed); } LogDl("usage: %f / %f, percent free %f", rate, m_NetcardSpeed, m_PercentFree); if (m_PercentFree < 0) { m_PercentFree = 0; } else if (m_PercentFree > MAX_BANDWIDTH_FRACTION) // never monopolize the net { m_PercentFree = MAX_BANDWIDTH_FRACTION; } } //------------------------------------------------------------------------ DWORD CNetworkInterface::BlockSizeFromInterval( SECONDS interval ) { NETWORK_RATE FreeBandwidth = GetInterfaceSpeed() * GetPercentFree() * interval; if (FreeBandwidth <= REQUEST_OVERHEAD) { return 0; } return FreeBandwidth - REQUEST_OVERHEAD; } CNetworkInterface::SECONDS CNetworkInterface::IntervalFromBlockSize( DWORD BlockSize ) { NETWORK_RATE FreeBandwidth = GetInterfaceSpeed() * GetPercentFree(); BlockSize += REQUEST_OVERHEAD; if (BlockSize / MAX_BLOCK_INTERVAL > FreeBandwidth ) { return -1; } return BlockSize / FreeBandwidth; } void CNetworkInterface::CalculateIntervalAndBlockSize( UINT64 MaxBlockSize ) { MaxBlockSize = min( MaxBlockSize, MAX_BLOCK_SIZE ); if (MaxBlockSize == 0) { m_BlockInterval = 0; m_BlockSize = 0; SetTimerInterval( m_BlockInterval ); LogDl( "block %d bytes, interval %f seconds", m_BlockSize, m_BlockInterval ); return; } // // Calculate new block size from the average interface speed. // DWORD OldState = m_state; m_BlockInterval = DEFAULT_BLOCK_INTERVAL; m_BlockSize = BlockSizeFromInterval( m_BlockInterval ); if (m_BlockSize > MaxBlockSize) { m_BlockSize = MaxBlockSize; m_BlockInterval = IntervalFromBlockSize( m_BlockSize ); ASSERT( m_BlockInterval > 0 ); } else if (m_BlockSize < MIN_BLOCK_SIZE) { m_BlockSize = min( MIN_BLOCK_SIZE, MaxBlockSize ); m_BlockInterval = IntervalFromBlockSize( m_BlockSize ); } if (m_BlockInterval < 0) { m_BlockSize = 0; } // // choose the new block download state. // if (m_BlockSize > 0) { m_state = DOWNLOADED_BLOCK; } else { // // The first time m_BlockSize is set to zero, retain the default interval. // If blocksize is zero twice in a row, expand to MAX_BLOCK_INTERVAL. // Then force a small download. // switch (OldState) { case DOWNLOADED_BLOCK: { m_BlockInterval = DEFAULT_BLOCK_INTERVAL; m_state = SKIPPED_ONE_BLOCK; break; } case SKIPPED_ONE_BLOCK: { m_BlockInterval = MAX_BLOCK_INTERVAL; m_state = SKIPPED_TWO_BLOCKS; break; } case SKIPPED_TWO_BLOCKS: { m_BlockSize = min( BUSY_BLOCK_SIZE, MaxBlockSize); m_BlockInterval = MAX_BLOCK_INTERVAL; m_state = DOWNLOADED_BLOCK; break; } default: ASSERT( 0 ); } } SetTimerInterval( m_BlockInterval ); LogDl( "block %d bytes, interval %f seconds", m_BlockSize, m_BlockInterval ); ASSERT( m_BlockSize <= MaxBlockSize ); } BOOL CNetworkInterface::SetTimerInterval( SECONDS interval ) { DWORD msec = interval*1000; if (msec <= 0) { msec = MIN_BLOCK_INTERVAL; } LogDl( "%d milliseconds", msec ); if (FALSE == m_Timer.Start( msec )) { return FALSE; } return TRUE; } HRESULT CNetworkInterface::FindInterfaceIndex( const TCHAR host[], DWORD * pIndex ) { //related to finding statistics /* Use GetBestInterface with some IP address to get the index. Double check that this index * occurs in the output of the IP Address table and look it up in the results of GetIfTable. */ #define AOL_ADAPTER _T("AOL Adapter") #define AOL_DIALUP_ADAPTER _T("AOL Dial-Up Adapter") BOOL bFound = FALSE; BOOL bAOL = FALSE; unsigned i; DWORD dwAddr; ULONG HostAddress; struct sockaddr_in dest; DWORD dwIndex = -1; static TCHAR szIntfName[512]; *pIndex = -1; try { // // Translate the host name into a SOCKADDR. // unsigned length = 3 * lstrlen(host); CAutoStringA AsciiHost ( new char[ length ]); if (! WideCharToMultiByte( CP_ACP, 0, // no flags host, -1, // use strlen AsciiHost.get(), length, // use strlen NULL, // no default char NULL // no default char )) { DWORD dwError = GetLastError(); LogError( "Unicode conversion failed %!winerr!", dwError ); return HRESULT_FROM_WIN32( dwError ); } HostAddress = inet_addr( AsciiHost.get() ); if (HostAddress == -1) { struct hostent *pHostEntry = gethostbyname( AsciiHost.get() ); if (pHostEntry == 0) { DWORD dwError = WSAGetLastError(); LogError( "failed to find host '%s': %!winerr!", AsciiHost.get(), dwError ); return HRESULT_FROM_WIN32( dwError ); } if (pHostEntry->h_addr_list[0] == NULL) { DWORD dwError = WSANO_DATA; LogError( "host address list empty '%s': %!winerr!", AsciiHost.get(), dwError ); return HRESULT_FROM_WIN32( dwError ); } HostAddress = *PULONG(pHostEntry->h_addr_list[0]); } } catch ( ComError err ) { LogError( "exception 0x%x finding server IP address", err.Error() ); return err.Error(); } //for remote addr dest.sin_addr.s_addr = HostAddress; dest.sin_family = AF_INET; dest.sin_port = 80; DWORD dwGetBestInterfaceError = GetBestInterface(dest.sin_addr.s_addr, &dwIndex); if (dwGetBestInterfaceError != NO_ERROR) { LogError( "GetBestInterface failed with error %!winerr!, might be Win95", dwGetBestInterfaceError); //manually parse the routing table ULONG size = 0; DWORD dwIpForwardError = GetIpForwardTable(NULL, &size, FALSE); if (dwIpForwardError != ERROR_INSUFFICIENT_BUFFER) { LogError( "sizing GetIpForwardTable failed %!winerr!", dwIpForwardError ); return HRESULT_FROM_WIN32( dwIpForwardError ); } auto_ptr pIpFwdTable((PMIB_IPFORWARDTABLE)new char[size]); if ( !pIpFwdTable.get() ) { LogError( "out of memory getting %d bytes", size); return E_OUTOFMEMORY; } dwIpForwardError = GetIpForwardTable(pIpFwdTable.get(), &size, TRUE); if (dwIpForwardError == NO_ERROR) //sort by dest addr { //perform bitwise AND of dest address with netmask and see if it matches network dest //todo check for multiple matches and then take longest mask for (i=0; i < pIpFwdTable->dwNumEntries; i++) { if ((dest.sin_addr.s_addr & pIpFwdTable->table[i].dwForwardMask) == pIpFwdTable->table[i].dwForwardDest) { dwIndex = pIpFwdTable->table[i].dwForwardIfIndex; break; } } if (dwIndex == -1) { // no match return HRESULT_FROM_WIN32( ERROR_NETWORK_UNREACHABLE ); } } else { LogError( "GetIpForwardTable failed with error %!winerr!, exiting", dwIpForwardError ); return HRESULT_FROM_WIN32( dwIpForwardError ); } } // // At this point dwIndex should be correct. // ASSERT( dwIndex != -1 ); #if DBG try { // // Discover the local IP address for the correct interface. // ULONG size = 0; DWORD dwGetIpAddr = GetIpAddrTable(NULL, &size, FALSE); if (dwGetIpAddr != ERROR_INSUFFICIENT_BUFFER) { LogError( "GetIpAddrTable #1 returned %!winerr!", dwGetIpAddr ); return HRESULT_FROM_WIN32( dwGetIpAddr ); } auto_ptr pAddrTable( (PMIB_IPADDRTABLE) new char[size] ); dwGetIpAddr = GetIpAddrTable(pAddrTable.get(), &size, TRUE); if (dwGetIpAddr != NO_ERROR) { LogError( "GetIpAddrTable #2 returned %!winerr!", dwGetIpAddr ); return HRESULT_FROM_WIN32( dwGetIpAddr ); } for (i=0; i < pAddrTable->dwNumEntries; i++) { if (dwIndex == pAddrTable->table[i].dwIndex) { in_addr address; address.s_addr = pAddrTable->table[i].dwAddr; LogDl( "Throttling on interface with IP address - %s", inet_ntoa( address )); break; } } if (i >= pAddrTable->dwNumEntries) { LogWarning( "can't find interface with index %d in the IP address table", dwIndex ); } } catch ( ComError err ) { LogWarning("unable to print the local IP address due to exception %x", err.Error() ); } #endif // DBG // // See if the adapter in question is the AOL adapter. If so, use the AOL dial-up adapter instead. // static MIB_IFROW s_TempRow; s_TempRow.dwIndex = dwIndex; DWORD dwEntryError = GetIfEntry( &s_TempRow ); if ( NO_ERROR != dwEntryError ) { LogError( "GetIfEntry(%d) returned %!winerr!", dwIndex, dwEntryError ); return HRESULT_FROM_WIN32( dwEntryError ); } if (lstrcmp( LPCWSTR(s_TempRow.bDescr), AOL_ADAPTER) == 0) { LogWarning( "found AOL adapter, searching for dial-up adapter..."); dwIndex = -1; ULONG size = 0; DWORD dwGetIfTableError = GetIfTable( NULL, &size, FALSE ); if (dwGetIfTableError != ERROR_INSUFFICIENT_BUFFER) { LogError( "GetIfTable #2 returned %!winerr!", dwGetIfTableError ); return HRESULT_FROM_WIN32( dwGetIfTableError ); } auto_ptr pIfTable( (PMIB_IFTABLE) new char[size] ); if ( !pIfTable.get() ) { LogError( "out of memory getting %d bytes", size); return E_OUTOFMEMORY; } dwGetIfTableError = GetIfTable( pIfTable.get(), &size, FALSE ); if ( NO_ERROR != dwGetIfTableError ) { LogError( "GetIfTable #2 returned %!winerr!", dwGetIfTableError ); return HRESULT_FROM_WIN32( dwGetIfTableError ); } for (i=0; i < pIfTable->dwNumEntries; ++i) { if (lstrcmp( LPCWSTR(pIfTable->table[i].bDescr), AOL_DIALUP_ADAPTER) == 0) { dwIndex = pIfTable->table[i].dwIndex; break; } } } ASSERT( dwIndex != -1 ); *pIndex = dwIndex; LogDl( "using interface index %d", dwIndex ); return S_OK; }