BOOL CManagedSearch::ExecuteDonkeyMesh(const DWORD /*tTicks*/, const DWORD tSecs)
{
	ASSUME_LOCK( SearchManager.m_pSection );

	CQuickLock oLock( HostCache.eDonkey.m_pSection );

	for ( CHostCacheIterator i = HostCache.eDonkey.Begin() ; i != HostCache.eDonkey.End() ; ++i )
	{
		CHostCacheHostPtr pHost = (*i);

		ASSERT( pHost->m_nProtocol == PROTOCOL_ED2K );

		// If this host is a neighbour, don't UDP to it
		if ( Neighbours.Get( pHost->m_pAddress ) )
			continue;

		// Make sure this host can be queried (now)
		if ( ! pHost->CanQuery( tSecs ) )
			continue;

		// Never requery eDonkey2000 servers
		DWORD tLastQuery;
		if ( m_pNodes.Lookup( pHost->m_pAddress.s_addr, tLastQuery ) )
			continue;

		// Set the last query time for this host for this search
		m_pNodes.SetAt( pHost->m_pAddress.s_addr, tSecs );

		// Record the query time on the host, for all searches
		pHost->m_tQuery = tSecs;

		// Create a packet in the appropriate format
		if ( CPacket* pPacket = m_pSearch->ToEDPacket( TRUE, pHost->m_nUDPFlags ) )
		{
			// Send the datagram if possible
			if ( Datagrams.Send( &pHost->m_pAddress, pHost->m_nPort + 4, pPacket, TRUE ) )
			{
				theApp.Message( MSG_DEBUG | MSG_FACILITY_SEARCH, _T("Sending UDP query to %s"), (LPCTSTR)CString( inet_ntoa( pHost->m_pAddress ) ) );

				return TRUE;
			}
		}
	}

	return FALSE;
}
// Takes a GUID, and a neighbour to except from the packet we will make
// Makes a Gnutella2 query web packet, containing the IP addresses of the computers we are connected to and from the Gnutella2 host cache
CG2Packet* CNeighboursWithG2::CreateQueryWeb(const Hashes::Guid& oGUID, bool bWithHubs, CNeighbour* pExcept, bool bDone)
{
	// Make a new Gnutella2 Query Ack packet
	CG2Packet* pPacket = CG2Packet::New( G2_PACKET_QUERY_ACK, TRUE );
	if ( ! pPacket )
		return NULL;

	// Start it with the text "TS" and the time now
	const DWORD tNow = static_cast< DWORD >( time( NULL ) );	// Number of seconds since 1970
	pPacket->WritePacket( G2_PACKET_TIMESTAMP, 4 );
	pPacket->WriteLongBE( tNow );

	// Write in header information about us
	pPacket->WritePacket( G2_PACKET_FROM_ADDRESS, 4 );
	pPacket->WriteLongLE( Network.m_pHost.sin_addr.S_un.S_addr );
	pPacket->WritePacket( G2_PACKET_RETRY_AFTER, 4 );
	pPacket->WriteLongBE( Settings.Gnutella2.QueryThrottle );

	if ( bDone )
	{
		pPacket->WritePacket( G2_PACKET_QUERY_DONE, 8 );
		pPacket->WriteLongLE( Network.m_pHost.sin_addr.S_un.S_addr );
		pPacket->WriteShortBE( htons( Network.m_pHost.sin_port ) );

		if ( bWithHubs )
		{
			const WORD nLeafs = (WORD)GetCount( PROTOCOL_G2, nrsConnected, ntLeaf );
			pPacket->WriteShortBE( nLeafs );

			// Loop through the connected computers
			for ( POSITION pos = GetIterator() ; pos ; )
			{
				// Get the neighbour object at this position, and move pos to the next one
				CG2Neighbour* pNeighbour = (CG2Neighbour*)GetNext( pos );

				// If this neighbour is running Gnutella2 software
				if ( pNeighbour->m_nProtocol == PROTOCOL_G2 &&	// The remote computer is running Gnutella2 software, and
					 pNeighbour->m_nNodeType != ntLeaf      &&	// Our connection to it is not down to a leaf, and
					 pNeighbour->m_nState >= nrsConnected   &&	// We've finished the handshake with it, and
					 pNeighbour != pExcept )					// This isn't the computer the caller warned us to except
				{
					// Write information about this connected computer into the packet
					pPacket->WritePacket( G2_PACKET_QUERY_DONE, 8 );
					pPacket->WriteLongLE( pNeighbour->m_pHost.sin_addr.S_un.S_addr );
					pPacket->WriteShortBE( htons( pNeighbour->m_pHost.sin_port ) );
					pPacket->WriteShortBE( (WORD)pNeighbour->m_nLeafCount );
				}
			}

			// Will put up to 3 or 25 IP addresses in the packet:
			// If caller didn't give us a computer to ignore make nCount 3, if it did give us an except make nCount 25
			int nCount = ( pExcept == NULL ) ? 3 : 25;

			CQuickLock oLock( HostCache.Gnutella2.m_pSection );

			// Loop, starting with the newest entry in the Gnutella2 host cache, then stepping to the one before that
			for ( CHostCacheIterator i = HostCache.Gnutella2.Begin() ; i != HostCache.Gnutella2.End() ; ++i )
			{
				CHostCacheHostPtr pHost = (*i);

				// If this host cache entry is good
				if ( pHost->CanQuote( tNow ) &&								// If this host cache entry hasn't expired, and
					 Get( pHost->m_pAddress ) == NULL &&					// We're connected to that IP address right now, and
					 HubHorizonPool.Find( &pHost->m_pAddress ) == NULL )	// The IP address is also in the hub horizon pool
				{
					// Add the IP address to the packet we're making, to encourage recipient to try this address
					pPacket->WritePacket( G2_PACKET_QUERY_SEARCH, 10 );
					pPacket->WriteLongLE( pHost->m_pAddress.S_un.S_addr );
					pPacket->WriteShortBE( pHost->m_nPort );
					pPacket->WriteLongBE( pHost->Seen() );

					// Lower the count, if it is then 0, leave the loop
					if ( ! --nCount ) break;
				}
			}

			// Give the packet we're making to our own hub horizon pool
			HubHorizonPool.AddHorizonHubs( pPacket );
		}
		else // No hubs
		{
			pPacket->WriteShortBE( 0 );
		}
	}

	// Finish the packet with a 0 byte and the guid the caller gave us, and return it
	pPacket->WriteByte( 0 );
	pPacket->Write( oGUID );

	return pPacket;
}
Exemple #3
0
void CEDClients::RunGlobalStatsRequests(DWORD tNow)
{
	// Don't send stat requests or time out servers if we're not stable
	if ( Network.IsFirewalled(CHECK_UDP) ) return;

	if ( m_nLastServerKey != 0 )
	{
		// We are waiting for a response
		if ( tNow > m_tLastServerStats + Settings.Connection.TimeoutHandshake )
		{
			// Timed out
			m_nLastServerKey = 0;
			theApp.Message( MSG_DEBUG, _T("Time-out waiting for ed2k server status") );

			CQuickLock pLock( HostCache.eDonkey.m_pSection );

			CHostCacheHostPtr pHost = HostCache.eDonkey.Find( &m_pLastServer );
			if ( pHost )
			{
				pHost->m_tFailure = pHost->m_tStats;

				pHost->m_nFailures ++;
				// If we've had multiple failures, remove the host
				if ( pHost->m_nFailures > 3 )
				{
					theApp.Message( MSG_INFO, _T("Removing ed2k server %s"), pHost->m_sName );
					HostCache.eDonkey.Remove( pHost );
				}
			}
			// Reset the timer so we query another server right away
			// m_tLastServerStats = 0;
		}
	}

	if ( tNow > m_tLastServerStats + Settings.eDonkey.StatsGlobalThrottle )		// Limit requests to every 30 minutes
	{
		// Get the current time (in seconds)
		const DWORD tSecs = static_cast< DWORD >( time( NULL ) );

		CQuickLock oLock( HostCache.eDonkey.m_pSection );

		// Loop through servers in the host cache
		for ( CHostCacheIterator i = HostCache.eDonkey.Begin() ; i != HostCache.eDonkey.End() ; ++i )
		{
			CHostCacheHostPtr pHost = (*i);

			//CString str;
			//str.Format( _T("  -Name:%s Last Stats:%d UDP flags:%08X"), pHost->m_sName, pHost->m_tStats, pHost->m_nUDPFlags );
			//theApp.Message( MSG_INFO, str );

			// Check if this server could be asked for stats
			if ( ( pHost->CanQuery( tSecs ) ) &&													// If it hasn't been searched recently
				 ( ( tSecs > pHost->m_tStats + Settings.eDonkey.StatsServerThrottle ) ||			// AND we have not checked this host in a week
				   ( ( pHost->m_nFailures > 0 ) && ( tSecs > pHost->m_tStats + 8*60*60 ) ) ) && 	// OR last check failed, have not checked in 8 hours
				 ( pHost->m_nUDPFlags == 0 || m_bAllServersDone ) )									// AND it has no flags set OR we have checked all servers
			{
				CSingleLock pLock( &Network.m_pSection );
				if ( ! pLock.Lock( 200 ) ) continue;

				// Don't ask current neighbours for stats
				if ( ! Neighbours.Get( pHost->m_pAddress ) )
				{
					// Send a request for stats to this server
					if ( ! pHost->m_sName.IsEmpty() )
						theApp.Message( MSG_INFO, _T("Sending status request to ed2k server %s"), pHost->m_sName );
					else
						theApp.Message( MSG_INFO, _T("Sending status request to ed2k server %s"), (LPCTSTR)CString( inet_ntoa( pHost->m_pAddress ) ) );

					RequestServerStatus( &pHost->m_pAddress, pHost->m_nPort );
					pHost->m_tStats = tSecs;
					m_tLastServerStats = tNow;
					m_pLastServer = pHost->m_pAddress;
					return;
				}
			}
		}

		m_bAllServersDone  = TRUE;		// We have checked all known servers, we may go back and re-query any that didn't respond.
		m_tLastServerStats = tNow;		// Try again later. (we don't want to keep running this section, it's a little slow)
	}
}
Exemple #4
0
BOOL CEDClients::OnServerStatus(const SOCKADDR_IN* pHost, CEDPacket* pPacket)
{
	DWORD nLen = pPacket->GetRemaining();
	if ( nLen < 4 )
		return FALSE;

	// Read in and check the key value to make sure we requested this update
	DWORD nKey = pPacket->ReadLongLE();

	CQuickLock oLock( HostCache.eDonkey.m_pSection );
	
	CHostCacheHostPtr pServer = HostCache.eDonkey.Find( &pHost->sin_addr );
	if ( pServer == NULL )
	{
		theApp.Message( MSG_WARNING, _T("eDonkey server %s:%u status received, but server not found in host cache"), (LPCTSTR)CString( inet_ntoa( pHost->sin_addr ) ), htons( pHost->sin_port ) );
		return FALSE;
	}
	if ( pServer->m_nKeyValue != nKey )
	{
		theApp.Message( MSG_WARNING, _T("eDonkey server %s:%u status received, but server key does not match"), (LPCTSTR)CString( inet_ntoa( pHost->sin_addr ) ), htons( pHost->sin_port ) );
		return FALSE;
	}

	// Assume UDP is stable
	Datagrams.SetStable();

	// Read in the status packet
	DWORD nUsers = 0, nFiles = 0, nMaxUsers = 0, nFileLimit = 1000, nUDPFlags = 0;
	if ( nLen >= 12 )
	{
		// Current users and files indexed
		nUsers = pPacket->ReadLongLE();
		nFiles = pPacket->ReadLongLE();
	}
	if ( nLen >= 16 )
	{
		// Maximum users allowed
		nMaxUsers = pPacket->ReadLongLE();
	}
	if ( nLen >= 24 )
	{
		// Client file limit. (Maximum files you can send to the server)
		nFileLimit = pPacket->ReadLongLE();	// Soft limit. (Files over this are ignored)
		pPacket->ReadLongLE();				// 'Hard' limit. (Obey previous, it saves bandwidth)
	}
	if ( nLen >= 28 )
	{
		// UDP Flags. (This is important, it determines search types, etc)
		nUDPFlags = pPacket->ReadLongLE();
	}
	if ( nLen >= 32 )
	{
		// Low ID users.
		pPacket->ReadLongLE(); // We don't use this
	}
	if ( nLen >= 40 )
	{
		// UDP Obfuscation Port
		pPacket->ReadShortLE(); // We don't use this
		// TCP Obfuscation Port
		pPacket->ReadShortLE(); // We don't use this
		// Server Key
		pPacket->ReadLongLE(); // We don't use this
	}

	// Update the server variables
	pServer->m_tAck			= 0;
	pServer->m_nFailures	= 0;
	pServer->m_tFailure		= 0;
	pServer->m_bCheckedLocally	= TRUE;
	pServer->m_nUserCount	= nUsers;
	pServer->m_nUserLimit	= nMaxUsers;
	pServer->m_nFileLimit	= nFileLimit;
	pServer->m_nUDPFlags	= nUDPFlags;

	if ( pServer->Seen() < pServer->m_tStats )
		HostCache.eDonkey.Update( pServer, 0, pServer->m_tStats );
	if ( nUDPFlags & ED2K_SERVER_UDP_UNICODE )
		pServer->m_nTCPFlags |= ED2K_SERVER_TCP_UNICODE;
	if ( nUDPFlags & ED2K_SERVER_UDP_GETSOURCES2 )
		pServer->m_nTCPFlags |= ED2K_SERVER_TCP_GETSOURCES2;

	HostCache.eDonkey.Update( pServer );

	theApp.Message( MSG_DEBUG, _T("eDonkey server %s:%u UDP flags: %s"), (LPCTSTR)CString( inet_ntoa( pHost->sin_addr ) ), htons( pHost->sin_port ), GetED2KServerUDPFlags( nUDPFlags ) );

	return TRUE;
}
// As the program runs, CNetwork::OnRun calls this method periodically and repeatedly
// Counts how many connections we have for each network and in each role, and connects to more from the host cache or disconnects from some
void CNeighboursWithConnect::Maintain()
{
	// Get the time
	const DWORD tTimer = GetTickCount();						// Time in ticks (milliseconds)
	const DWORD tNow   = static_cast< DWORD >( time( NULL ) );	// Time in seconds

	// Don't initiate neighbour connections too quickly if connections are limited
	if ( Settings.Connection.ConnectThrottle && tTimer >= m_tLastConnect && tTimer <= m_tLastConnect + Settings.Connection.ConnectThrottle )
		return;

	DWORD nCount[ PROTOCOL_LAST ][3] = {}, nLimit[ PROTOCOL_LAST ][3] = {};

	// Loop down the list of connected neighbours, sorting each by network and role and counting it
	// Also prune leaf to leaf connections, which shouldn't happen
	for ( POSITION pos = GetIterator() ; pos ; )
	{
		// Get the next neighbour in the list
		CNeighbour* pNeighbour = GetNext( pos );

		// We're done with the handshake and connected to this remote computer
		if ( pNeighbour->m_nState == nrsConnected )
		{
			if ( pNeighbour->m_nNodeType != ntHub && m_bG2Leaf && pNeighbour->m_nProtocol == PROTOCOL_G2 )
			{
				// We're both Gnutella2 leaves:
				// Two leaves shouldn't connect, disconnect
				pNeighbour->Close( IDS_CONNECTION_PEERPRUNE );
			}
			else if ( pNeighbour->m_nNodeType != ntHub && m_bG1Leaf && pNeighbour->m_nProtocol == PROTOCOL_G1 )
			{
				// We're both Gnutella leaves:
				// Two leaves shouldn't connect, disconnect
				pNeighbour->Close( IDS_CONNECTION_PEERPRUNE );
			}
			else if ( pNeighbour->m_nNodeType != ntLeaf )
			{
				// This connection is to a hub above us, or a hub just like us:
				// Count one more hub for this connection's protocol only if we've been connected for several seconds.
				if ( tTimer > pNeighbour->m_tConnected + 8000 )
					nCount[ pNeighbour->m_nProtocol ][ ntHub ]++;
			}
			else
			{
				// We must be a hub, and this connection must be down to a leaf:
				// Count one more leaf for this connection's protocol
				nCount[ pNeighbour->m_nProtocol ][ ntLeaf ]++;
			}
		}
		else if ( pNeighbour->m_nState < nrsConnected )
		{
			// We're still going through the handshake with this remote computer
			// Count one more connection in the 0 column for this protocol
			nCount[ pNeighbour->m_nProtocol ][ ntNode ]++;	// ntNode is 0
		}
	}

	// Set our "promoted to hub" timer
	if ( ! m_bG2Hub )
		m_tHubG2Promotion = 0;			// If we're not a hub, time promoted is 0
	else if ( m_tHubG2Promotion == 0 )
		m_tHubG2Promotion = tNow;		// If we've just been promoted, set the timer

	// Check if we have verified if we make a good G2 hub
	if ( ! Settings.Gnutella2.HubVerified && m_tHubG2Promotion > 0 && Network.IsConnected() )
	{
		// If we have been a hub for at least 8 hours
		if ( tNow > m_tHubG2Promotion + 8 * 60 * 60 )
		{
			// And we're loaded ( 75% capacity ), then we probably make a pretty good hub
			if ( nCount[ PROTOCOL_G2 ][ ntHub ] > ( Settings.Gnutella2.NumLeafs * 3 / 4 ) )
				Settings.Gnutella2.HubVerified = true;
		}
	}

	if ( ! Settings.Gnutella1.Enabled )
	{
		// Set the limit as no Gnutella hub or leaf connections allowed at all
		nLimit[ PROTOCOL_G1 ][ ntHub ] = nLimit[ PROTOCOL_G1 ][ ntLeaf ] = 0;
	}
	else if ( m_bG1Leaf )	// We're a leaf on the Gnutella network
	{
		nLimit[ PROTOCOL_G1 ][ ntHub ] = Settings.Gnutella1.NumHubs;		// 3 ultrapeers by default
	}
	else	// We're an ultrapeer on the Gnutella network
	{
		// Set the limit for Gnutella ultrapeer connections as whichever number from settings is bigger, peers or hubs
		nLimit[ PROTOCOL_G1 ][ ntHub ] = max( Settings.Gnutella1.NumPeers, Settings.Gnutella1.NumHubs );	// Defaults are 32 and 3

		// Set the limit for Gnutella leaf connections from settings
		nLimit[ PROTOCOL_G1 ][ ntLeaf ] = Settings.Gnutella1.NumLeafs;		// 50 leaves by default
	}

	if ( ! Settings.Gnutella2.Enabled )
	{
		// Set the limit as no Gnutella2 hub or leaf connections allowed at all
		nLimit[ PROTOCOL_G2 ][ ntHub ] = nLimit[ PROTOCOL_G2 ][ ntLeaf ] = 0;
	}
	else if ( m_bG2Leaf )	// We're a leaf on the Gnutella2 network
	{
		// Set the limit for Gnutella2 hub connections from settings, should be no more than 3
		nLimit[ PROTOCOL_G2 ][ ntHub ] = Settings.Gnutella2.NumHubs;		// 2 hubs by default
	}
	else	// We're a hub on the Gnutella2 network
	{
		// Set the limit for G2 hub connections as whichever number from settings is bigger, peers or hubs
		nLimit[ PROTOCOL_G2 ][ ntHub ] = max( Settings.Gnutella2.NumPeers, Settings.Gnutella2.NumHubs );	// Defaults are 6 and 2

		// Set the limit for G2 leaf connections from user settings
		nLimit[ PROTOCOL_G2 ][ ntLeaf ] = Settings.Gnutella2.NumLeafs;		// 300 leaves by default
	}

	nLimit[ PROTOCOL_ED2K ][ ntHub ] = Settings.eDonkey.Enabled ? Settings.eDonkey.NumServers : 0;		// 1 server by default

	nLimit[ PROTOCOL_DC ][ ntHub ] = Settings.DC.Enabled ? Settings.DC.NumServers : 0;					// 1 hub by default

	// Add the count of connections where we don't know the network yet to the 0 column of both Gnutella and Gnutella2
	nCount[ PROTOCOL_G1 ][ ntNode ] += nCount[ PROTOCOL_NULL ][ ntNode ];
	nCount[ PROTOCOL_G2 ][ ntNode ] += nCount[ PROTOCOL_NULL ][ ntNode ];

	// Connect to more computers or disconnect from some to get the connection counts where settings wants them to be
	for ( PROTOCOLID nProtocol = PROTOCOL_NULL ; nProtocol < PROTOCOL_LAST ; ++nProtocol )		// Loop once for each protocol
	{
		// If we're connected to a hub of this protocol, store the tick count now in m_tPresent for this protocol
		if ( nCount[ nProtocol ][ ntHub ] > 0 ) m_tPresent[ nProtocol ] = tNow;

		// If we don't have enough hubs for this protocol
		if ( nCount[ nProtocol ][ ntHub ] < nLimit[ nProtocol ][ ntHub ] )
		{
			// Don't try to connect to G1 right away, wait a few seconds to reduce the number of connections
			if ( nProtocol != PROTOCOL_G2 && Settings.Gnutella2.Enabled )
			{
				if ( ! Network.ReadyToTransfer( tTimer ) ) return;
			}

			// We are going to try to connect to a computer running Gnutella or Gnutella2 software
			DWORD nAttempt;
			if ( nProtocol == PROTOCOL_ED2K )
			{
				// For ed2k we try one attempt at a time to begin with, but we can step up to
				// 2 at a time after a few seconds if the FastConnect option is selected.
				if ( Settings.eDonkey.FastConnect && Network.ReadyToTransfer( tTimer ) )
					nAttempt = 2;
				else
					nAttempt = 1;
			}
			else if ( nProtocol == PROTOCOL_DC )
			{
				// DC++ Slow connecting
				nAttempt = 1;
			}
			else
			{
				// For Gnutella and Gnutella2, try connection to the number of free slots multiplied by the connect factor from settings
				nAttempt = ( nLimit[ nProtocol ][ ntHub ] - nCount[ nProtocol ][ ntHub ] );
				nAttempt *= Settings.Gnutella.ConnectFactor;
			}

			// Lower the needed hub number to avoid hitting Windows XP Service Pack 2's half open connection limit
			nAttempt = min( nAttempt, ( Settings.Downloads.MaxConnectingSources - 1 ) );

			CHostCacheList* pCache = HostCache.ForProtocol( nProtocol );

			CSingleLock oLock( &pCache->m_pSection, FALSE );
			if ( ! oLock.Lock( 250 ) )
				return;

			// Handle priority servers
			// Loop into the host cache until we have as many handshaking connections as we need hub connections
			for ( CHostCacheIterator i = pCache->Begin() ;
				i != pCache->End() && nCount[ nProtocol ][ ntNode ] < nAttempt ;
				++i )
			{
				CHostCacheHostPtr pHost = (*i);

				// If we can connect to this priority host, try it
				if ( pHost->m_bPriority &&
					 pHost->CanConnect( tNow ) &&
					 pHost->ConnectTo( TRUE ) )
				{
					m_tPriority[ nProtocol ] = tNow;

					pHost->m_nFailures = 0;
					pHost->m_tFailure = 0;
					pHost->m_bCheckedLocally = TRUE;

					// Count that we now have one more connection, and we don't know its network role yet
					nCount[ nProtocol ][ ntNode ]++;

					// Prevent queries while we connect with this computer (do)
					pHost->m_tQuery = tNow;

					// If settings wants to limit how frequently this method can run
					if ( Settings.Connection.ConnectThrottle )
					{
						m_tLastConnect = tTimer;
						return;
					}
				}
			}

			// 10 second delay between priority and regular servers
			if ( tNow > m_tPriority[ nProtocol ] + 10 )
			{
				// Handle regular servers, if we need more connections for this network, get IP addresses from the host cache and try to connect to them
				for ( CHostCacheIterator i = pCache->Begin() ;
					i != pCache->End() && nCount[ nProtocol ][ ntNode ] < nAttempt ;
					++i )
				{
					CHostCacheHostPtr pHost = (*i);

					// If we can connect to this IP address from the host cache, try it
					if ( ! pHost->m_bPriority &&
						pHost->CanConnect( tNow ) &&
						pHost->ConnectTo( TRUE ) )
					{
						// Make sure the connection we just made matches the protocol we're looping for right now
						//ASSERT( pHost->m_nProtocol == nProtocol );
						pHost->m_nFailures = 0;
						pHost->m_tFailure = 0;
						pHost->m_bCheckedLocally = TRUE;

						// Count that we now have one more handshaking connection for this network
						nCount[ nProtocol ][ ntNode ]++;

						// Prevent queries while we log on (do)
						pHost->m_tQuery = tNow;

						// If settings wants to limit how frequently this method can run
						if ( Settings.Connection.ConnectThrottle )
						{
							m_tLastConnect = tTimer;
							return;
						}
					}
				}
			}

			// If we don't have any handshaking connections for this network, and we've been connected to a hub for more than 30 seconds
			if ( nCount[ nProtocol ][ ntNode ] == 0 ||			// We don't have any handshaking connections for this network, or
				 tNow > m_tPresent[ nProtocol ] + 30 )			// We've been connected to a hub for more than 30 seconds
			{
				const DWORD tDiscoveryLastExecute = DiscoveryServices.LastExecute();

				if ( nProtocol == PROTOCOL_G2 && Settings.Gnutella2.Enabled )
				{
					// We're looping for Gnutella2 right now
					// Execute the discovery services (do)
					if ( pCache->IsEmpty() && tNow >= tDiscoveryLastExecute + 8 )
						DiscoveryServices.Execute( TRUE, PROTOCOL_G2, 1 );
				}
				else if ( nProtocol == PROTOCOL_G1 && Settings.Gnutella1.Enabled )
				{
					// We're looping for Gnutella right now
					// If the Gnutella host cache is empty (do), execute discovery services (do)
					if ( pCache->IsEmpty() && tNow >= tDiscoveryLastExecute + 8 )
						DiscoveryServices.Execute( TRUE, PROTOCOL_G1, 1 );
				}
			}
		}
		else if ( nCount[ nProtocol ][ ntHub ] > nLimit[ nProtocol ][ ntHub ] ) 	// We're over the limit we just calculated
		{
			// Otherwise we have too many hub connections for this protocol,
			// so find the hub we connected to most recently to remove it.
			CNeighbour* pNewest = NULL;
			for ( POSITION pos = GetIterator() ; pos ; )
			{
				// Loop through the list of neighbours
				CNeighbour* pNeighbour = GetNext( pos );

				// If this is a hub connection that connected to us recently
				if ( ( pNeighbour->m_nNodeType != ntLeaf ) &&		// If this connection isn't down to a leaf, and
					 ( pNeighbour->m_nProtocol == nProtocol ) &&	// This connection is for the protocol we're looping on right now, and
					 ( pNeighbour->m_bAutomatic ||					// The neighbour is automatic, or
					  ! pNeighbour->m_bInitiated ||					// The neighbour connected to us, or
					  nLimit[ nProtocol ][ ntHub ] == 0 ) ) 		// We're not supposed to be connected to this network at all
				{
					// If this is the newest hub, remember it.
					if ( pNewest == NULL || pNeighbour->m_tConnected > pNewest->m_tConnected )
						pNewest = pNeighbour;
				}
			}

			// Disconnect from one hub
			if ( pNewest ) pNewest->Close();	// Close the connection
		}

		// If we're over our leaf connection limit for this network
		if ( nCount[ nProtocol ][ ntLeaf ] > nLimit[ nProtocol ][ ntLeaf ] )
		{
			// Find the leaf we most recently connected to
			CNeighbour* pNewest = NULL;
			for ( POSITION pos = GetIterator() ; pos ; )
			{
				// Loop for each neighbour in the list
				CNeighbour* pNeighbour = GetNext( pos );

				// This connection is down to a leaf and the protocol is correct
				if ( pNeighbour->m_nNodeType == ntLeaf && pNeighbour->m_nProtocol == nProtocol )
				{
					// If we haven't found the newest yet, or this connection is younger than the current newest, this is it
					if ( pNewest == NULL || pNeighbour->m_tConnected > pNewest->m_tConnected )
						pNewest = pNeighbour;
				}
			}

			// Disconnect from one leaf
			if ( pNewest ) pNewest->Close();	// Close the connection
		}
	}
}
BOOL CManagedSearch::ExecuteG2Mesh(const DWORD /*tTicks*/, const DWORD tSecs)
{
	ASSUME_LOCK( SearchManager.m_pSection );

	// Look at all known Gnutella2 hubs, newest first

	CQuickLock oLock( HostCache.Gnutella2.m_pSection );

	for ( CHostCacheIterator i = HostCache.Gnutella2.Begin() ;
		i != HostCache.Gnutella2.End() ; ++i )
	{
		CHostCacheHostPtr pHost = (*i);

		// Must be Gnutella2
		ASSERT( pHost->m_nProtocol == PROTOCOL_G2 );

		// If this host is a neighbour, don't UDP to it
		if ( Neighbours.Get( pHost->m_pAddress ) )
			continue;

		// If this host can't be queried now, don't query it
		if ( ! pHost->CanQuery( tSecs ) )
			continue;

		// Check if we have an appropriate query key for this host,
		// and if so, record the receiver address
		SOCKADDR_IN* pReceiver = NULL;

		if ( pHost->m_nKeyValue == 0 )
		{
			// We already know we don't have a key, pretty simple
		}
		else if ( ! Network.IsFirewalled(CHECK_UDP) )
		{
			// If we are "stable", we have to TX/RX our own UDP traffic,
			// so we must have a query key for the local addess
			if ( pHost->m_nKeyHost == Network.m_pHost.sin_addr.S_un.S_addr )
				pReceiver = &Network.m_pHost;
			else
				pHost->m_nKeyValue = 0;
		}
		else
		{
			// Make sure we have a query key via one of our neighbours,
			// and ensure we have queried this neighbour
			if ( CNeighbour* pNeighbour = Neighbours.Get( *(IN_ADDR*)&pHost->m_nKeyHost ) )
			{
				DWORD nTemp;
				if ( m_pNodes.Lookup( pHost->m_nKeyHost, nTemp ) )
					pReceiver = &pNeighbour->m_pHost;
				else
					continue;
			}
			else
			{
				pHost->m_nKeyValue = 0;
			}
		}

		// Now, if we still have a query key, send the query
		if ( pHost->m_nKeyValue != 0 )
		{
			DWORD tLastQuery;
			ASSERT( pReceiver != NULL );

			// Lookup the host
			if ( m_pNodes.Lookup( pHost->m_pAddress.s_addr, tLastQuery ) )
			{
				// Check per-hub re-query time
				DWORD nFrequency;

				if ( m_nPriority >= spLowest )
				{
					// Low priority "auto find" sources
					if ( m_pSearch->m_oSHA1 )		// Has SHA1- probably exists on G2
						nFrequency = 16 * 60 * 60;
					else							// Reduce frequency if no SHA1.
						nFrequency = 32 * 60 * 60;
				}
				else
					nFrequency = Settings.Gnutella2.RequeryDelay * ( m_nPriority + 1 );

				if ( tSecs - tLastQuery < nFrequency )
					continue;
			}

			// Set the last query time for this host for this search
			m_pNodes.SetAt( pHost->m_pAddress.s_addr, tSecs );

			// Record the query time on the host, for all searches
			pHost->m_tQuery = tSecs;
			if ( pHost->m_tAck == 0 )
				pHost->m_tAck = tSecs;

			// Try to create a packet
			m_pSearch->m_bAndG1 = ( Settings.Gnutella1.Enabled && m_bAllowG1 );

			if ( CPacket* pPacket = m_pSearch->ToG2Packet( pReceiver, pHost->m_nKeyValue ) )
			{
				if ( Datagrams.Send( &pHost->m_pAddress, pHost->m_nPort, pPacket, TRUE, this, TRUE ) )
				{
					theApp.Message( MSG_DEBUG | MSG_FACILITY_SEARCH, _T("Querying %s"), (LPCTSTR)CString( inet_ntoa( pHost->m_pAddress ) ) );
					return TRUE;
				}
			}
		}
		else if ( tSecs - pHost->m_tKeyTime >= max( Settings.Gnutella2.QueryThrottle * 5ul, 5ul * 60ul ) )
		{
			// Timing wise, we can request a query key now --
			// but first we must figure out who should be the receiver

			CNeighbour* pCacheHub = NULL;
			pReceiver = NULL;

			if ( ! Network.IsFirewalled( CHECK_UDP ) )
			{
				// If we are stable, we must be the receiver
				pReceiver = &Network.m_pHost;
			}
			else
			{
				// Otherwise, we need to find a neighbour G2 hub who has acked this query already
				for ( POSITION pos = Neighbours.GetIterator() ; pos ; pCacheHub = NULL )
				{
					pCacheHub = Neighbours.GetNext( pos );
					DWORD nTemp;

					if ( m_pNodes.Lookup( pCacheHub->m_pHost.sin_addr.s_addr, nTemp ) )
					{
						if ( pCacheHub->m_nProtocol == PROTOCOL_G2 &&
							 pCacheHub->m_nNodeType == ntHub )
						{
							pReceiver = &pCacheHub->m_pHost;
							if ( ! ((CG2Neighbour*)pCacheHub)->m_bCachedKeys )
								pCacheHub = NULL;
							break;
						}
					}
				}
			}

			// If we found a receiver, we can ask for the query key
			if ( pCacheHub != NULL )
			{
				// The receiver is a cache-capable hub, so we ask it to return a cached key, or fetch a fresh one
				if ( CG2Packet* pPacket = CG2Packet::New( G2_PACKET_QUERY_KEY_REQ, TRUE ) )
				{
					pPacket->WritePacket( G2_PACKET_QUERY_ADDRESS, 6 );
					pPacket->WriteLongLE( pHost->m_pAddress.S_un.S_addr );
					pPacket->WriteShortBE( pHost->m_nPort );

					if ( pCacheHub->Send( pPacket ) )
					{
						if ( pHost->m_tAck == 0 )
							pHost->m_tAck = tSecs;
						pHost->m_tKeyTime = tSecs;
						pHost->m_nKeyValue = 0;

						theApp.Message( MSG_DEBUG | MSG_FACILITY_SEARCH, _T("Requesting query key from %s through %s"),
							(LPCTSTR)CString( inet_ntoa( pHost->m_pAddress ) ), (LPCTSTR)CString( inet_ntoa( pReceiver->sin_addr ) ) );
						return TRUE;
					}
				}
			}
			else if ( pReceiver != NULL )
			{
				// We need to transmit directly to the remote query host
				if ( CG2Packet* pPacket = CG2Packet::New( G2_PACKET_QUERY_KEY_REQ, TRUE ) )
				{
					if ( pReceiver != &Network.m_pHost )
					{
						// We are not the receiver, so include receiver address
						pPacket->WritePacket( G2_PACKET_REQUEST_ADDRESS, 6 );
						pPacket->WriteLongLE( pReceiver->sin_addr.S_un.S_addr );
						pPacket->WriteShortBE( ntohs( pReceiver->sin_port ) );
					}

					if ( Datagrams.Send( &pHost->m_pAddress, pHost->m_nPort, pPacket, TRUE, NULL, FALSE ) )
					{
						if ( pHost->m_tAck == 0 )
							pHost->m_tAck = tSecs;
						pHost->m_tKeyTime = tSecs;
						pHost->m_nKeyValue = 0;

						if ( pReceiver == &Network.m_pHost )
						{
							theApp.Message( MSG_DEBUG | MSG_FACILITY_SEARCH, _T("Requesting query key from %s"),
								(LPCTSTR)CString( inet_ntoa( pHost->m_pAddress ) ) );
						}
						else
						{
							theApp.Message( MSG_DEBUG | MSG_FACILITY_SEARCH, _T("Requesting query key from %s for %s"),
								(LPCTSTR)CString( inet_ntoa( pHost->m_pAddress ) ), (LPCTSTR)CString( inet_ntoa( pReceiver->sin_addr ) ) );
						}
						return TRUE;
					}
				}
			}
		}
	}

	return FALSE;
}