|
|
/************************************************************************
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 <malloc.h>
#include <limits.h>
#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<MIB_IPFORWARDTABLE> 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<MIB_IPADDRTABLE> 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<MIB_IFTABLE> 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; }
|