2 // This file is part of the aMule Project.
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2008 Merkur ( devs@emule-project.net / http://www.emule-project.net )
7 // Any parts of this program derived from the xMule, lMule or eMule project,
8 // or contributed by third-party developers are copyrighted by their
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation; either version 2 of the License, or
14 // (at your option) any later version.
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 #include "ServerConnect.h" // Interface declarations.
28 #include <protocol/Protocols.h>
29 #include <protocol/ed2k/ClientSoftware.h> // Sometimes we reply with TCP packets.
30 #include <tags/ClientTags.h>
31 #include <common/ClientVersion.h>
32 #include <common/EventIDs.h>
34 #include "SearchList.h" // Needed for CSearchList
35 #include "ServerUDPSocket.h" // Needed for CServerUDPSocket
36 #include "SharedFileList.h" // Needed for CSharedFileList
37 #include "Packet.h" // Needed for CTag
38 #include "MemFile.h" // Needed for CMemFile
39 #include "ServerSocket.h" // Needed for CServerSocket
40 #include "ListenSocket.h" // Needed for CListenSocket
41 #include "Server.h" // Needed for CServer
42 #include "amule.h" // Needed for theApp
43 #include "ServerList.h" // Needed for CServerList
44 #include "Preferences.h" // Needed for CPreferences
45 #include "Statistics.h" // Needed for theStats
47 #include "GuiEvents.h" // Needed for Notify_*
48 #include "IPFilter.h" // Needed for theApp->ipfilter->IsReady()
49 #include <common/Format.h>
52 //#define DEBUG_CLIENT_PROTOCOL
55 void CServerConnect::TryAnotherConnectionrequest()
57 if (m_recurseTryAnotherConnectionrequest
) {
60 if ( connectionattemps
.size() < (unsigned)(( thePrefs::IsSafeServerConnectEnabled()) ? 1 : 2) ) {
62 CServer
* next_server
= used_list
->GetNextServer(m_bTryObfuscated
);
64 if ( thePrefs::AutoConnectStaticOnly() ) {
65 while (next_server
&& !next_server
->IsStaticMember()) {
66 next_server
= used_list
->GetNextServer(m_bTryObfuscated
);
71 if ( connectionattemps
.empty() ) {
72 m_recurseTryAnotherConnectionrequest
= true;
73 if (m_bTryObfuscated
&& !thePrefs::IsClientCryptLayerRequired()){
74 AddLogLineM(true, _("Failed to connect to all obfuscated servers listed. Making another pass without obfuscation."));
75 // try all servers on the non-obfuscated port next
76 m_bTryObfuscated
= false;
77 ConnectToAnyServer( false, true);
79 AddLogLineM(true, _("Failed to connect to all servers listed. Making another pass."));
80 ConnectToAnyServer( false );
82 m_recurseTryAnotherConnectionrequest
= false;
87 ConnectToServer(next_server
, true, !m_bTryObfuscated
);
91 void CServerConnect::ConnectToAnyServer(bool prioSort
, bool bNoCrypt
)
93 if (!thePrefs::GetNetworkED2K()){
94 AddLogLineM(true,_("eD2k network disabled on preferences, not connecting."));
97 if (!theApp
->ipfilter
->IsReady()) {
98 // do it later when ipfilter is up
99 theApp
->ipfilter
->ConnectToAnyServerWhenReady();
106 singleconnecting
= false;
107 m_bTryObfuscated
= thePrefs::IsServerCryptLayerTCPRequested() && !bNoCrypt
;
109 // Barry - Only auto-connect to static server option
110 if (thePrefs::AutoConnectStaticOnly()) {
111 bool anystatic
= false;
112 CServer
*next_server
;
113 used_list
->ResetServerPos();
114 while ((next_server
= used_list
->GetNextServer(false)) != NULL
) {
115 if (next_server
->IsStaticMember()) {
122 AddLogLineM(true,_("No valid servers to which to connect found in server list"));
127 if ( thePrefs::Score() && prioSort
) {
131 used_list
->ResetServerPos();
133 if (used_list
->GetServerCount()==0 ) {
135 AddLogLineM(true,_("No valid servers to which to connect found in server list"));
139 theApp
->listensocket
->Process();
141 TryAnotherConnectionrequest();
145 void CServerConnect::ConnectToServer(CServer
* server
, bool multiconnect
, bool bNoCrypt
)
147 if (!thePrefs::GetNetworkED2K()){
148 AddLogLineM(true,_("eD2k network disabled on preferences, not connecting."));
157 singleconnecting
= !multiconnect
;
159 CServerSocket
* newsocket
= new CServerSocket(this, thePrefs::GetProxyData());
160 m_lstOpenSockets
.push_back(newsocket
);
161 newsocket
->ConnectToServer(server
, bNoCrypt
);
163 connectionattemps
[GetTickCount()] = newsocket
;
167 void CServerConnect::StopConnectionTry()
169 connectionattemps
.clear();
171 singleconnecting
= false;
173 if (m_idRetryTimer
.IsRunning())
175 m_idRetryTimer
.Stop();
178 // close all currenty opened sockets except the one which is connected to our current server
179 for(SocketsList::iterator it
= m_lstOpenSockets
.begin(); it
!= m_lstOpenSockets
.end(); ) {
180 CServerSocket
*pSck
= *it
++;
181 if (pSck
== connectedsocket
) // don't destroy socket which is connected to server
183 if (pSck
->m_bIsDeleting
== false) // don't destroy socket if it is going to destroy itself later on
188 void CServerConnect::ConnectionEstablished(CServerSocket
* sender
)
190 if (connecting
== false)
192 // we are already connected to another server
193 DestroySocket(sender
);
197 if (sender
->GetConnectionState() == CS_WAITFORLOGIN
) {
198 AddLogLineM(false, CFormat( _("Connected to %s (%s:%i)") )
199 % sender
->cur_server
->GetListName()
200 % sender
->cur_server
->GetFullIP()
201 % sender
->cur_server
->GetPort() );
204 CServer
* update
= theApp
->serverlist
->GetServerByAddress( sender
->cur_server
->GetAddress(), sender
->cur_server
->GetPort() );
206 update
->ResetFailedCount();
207 Notify_ServerRefresh( update
);
211 data
.WriteHash(thePrefs::GetUserHash());
212 // Why pass an ID, if we are loggin in?
213 data
.WriteUInt32(GetClientID());
214 data
.WriteUInt16(thePrefs::GetPort());
215 data
.WriteUInt32(4); // tagcount
217 // Kry - Server doesn't support VBT tags afaik.
218 // Not to mention we don't know its flags yet
220 CTagString
tagname(CT_NAME
,thePrefs::GetUserNick());
221 tagname
.WriteTagToFile(&data
);
223 CTagInt32
tagversion(CT_VERSION
,EDONKEYVERSION
);
224 tagversion
.WriteTagToFile(&data
);
226 uint32 dwCryptFlags
= 0;
228 if (thePrefs::IsClientCryptLayerSupported()) {
229 dwCryptFlags
|= SRVCAP_SUPPORTCRYPT
;
232 if (thePrefs::IsClientCryptLayerRequested()) {
233 dwCryptFlags
|= SRVCAP_REQUESTCRYPT
;
236 if (thePrefs::IsClientCryptLayerRequired()) {
237 dwCryptFlags
|= SRVCAP_REQUIRECRYPT
;
240 // FLAGS for server connection
241 CTagInt32
tagflags(CT_SERVER_FLAGS
, CAPABLE_ZLIB
249 tagflags
.WriteTagToFile(&data
);
251 // eMule Version (14-Mar-2004: requested by lugdunummaster (need for LowID clients which have no chance
252 // to send an Hello packet to the server during the callback test))
253 CTagInt32
tagMuleVersion(CT_EMULE_VERSION
,
255 make_full_ed2k_version(VERSION_MJR
, VERSION_MIN
, VERSION_UPDATE
)
257 tagMuleVersion
.WriteTagToFile(&data
);
259 CPacket
* packet
= new CPacket(data
, OP_EDONKEYPROT
, OP_LOGINREQUEST
);
260 #ifdef DEBUG_CLIENT_PROTOCOL
261 AddLogLineM(true,wxT("Client: OP_LOGINREQUEST"));
262 AddLogLineM(true,wxString(wxT(" Hash : ")) << thePrefs::GetUserHash().Encode());
263 AddLogLineM(true,wxString(wxT(" ClientID : ")) << GetClientID());
264 AddLogLineM(true,wxString(wxT(" Port : ")) << thePrefs::GetPort());
265 AddLogLineM(true,wxString(wxT(" User Nick: ")) << thePrefs::GetUserNick());
266 AddLogLineM(true,wxString(wxT(" Edonkey : ")) << EDONKEYVERSION
);
268 theStats::AddUpOverheadServer(packet
->GetPacketSize());
269 SendPacket(packet
, true, sender
);
270 } else if (sender
->GetConnectionState() == CS_CONNECTED
){
271 theStats::AddReconnect();
272 theStats::GetServerConnectTimer()->ResetTimer();
274 AddLogLineM(true, CFormat( _("Connection established on: %s") ) % sender
->cur_server
->GetListName());
275 connectedsocket
= sender
;
279 CServer
* update
= theApp
->serverlist
->GetServerByAddress(connectedsocket
->cur_server
->GetAddress(),sender
->cur_server
->GetPort());
281 Notify_ServerHighlight(update
, true);
284 theApp
->sharedfiles
->ClearED2KPublishInfo();
286 Notify_ServerRemoveDead();
288 // tecxx 1609 2002 - serverlist update
289 if (thePrefs::AddServersFromServer()) {
290 CPacket
* packet
= new CPacket(OP_GETSERVERLIST
, 0, OP_EDONKEYPROT
);
291 theStats::AddUpOverheadServer(packet
->GetPacketSize());
292 SendPacket(packet
, true);
293 #ifdef DEBUG_CLIENT_PROTOCOL
294 AddLogLineM(true,wxT("Client: OP_GETSERVERLIST"));
299 theApp
->ShowConnectionState();
303 bool CServerConnect::SendPacket(CPacket
* packet
,bool delpacket
, CServerSocket
* to
)
307 connectedsocket
->SendPacket(packet
, delpacket
, true);
317 to
->SendPacket(packet
, delpacket
, true);
323 bool CServerConnect::SendUDPPacket(CPacket
* packet
, CServer
* host
, bool delpacket
, bool rawpacket
, uint16 port_offset
)
326 serverudpsocket
->SendPacket(packet
, host
, delpacket
, rawpacket
, port_offset
);
327 } else if (delpacket
) {
335 void CServerConnect::ConnectionFailed(CServerSocket
* sender
)
337 if (connecting
== false && sender
!= connectedsocket
)
339 // just return, cleanup is done by the socket itself
343 CServer
* pServer
= theApp
->serverlist
->GetServerByAddress(sender
->cur_server
->GetAddress(), sender
->cur_server
->GetPort());
344 switch (sender
->GetConnectionState()){
346 AddLogLineM(true, _("Fatal Error while trying to connect. Internet connection might be down"));
348 case CS_DISCONNECTED
:
349 theApp
->sharedfiles
->ClearED2KPublishInfo();
350 AddLogLineM(false,CFormat( _("Lost connection to %s (%s:%i)") )
351 % sender
->cur_server
->GetListName()
352 % sender
->cur_server
->GetFullIP()
353 % sender
->cur_server
->GetPort() );
356 Notify_ServerHighlight(pServer
, false);
360 AddLogLineM(false, CFormat( _("%s (%s:%i) appears to be dead.") )
361 % sender
->cur_server
->GetListName()
362 % sender
->cur_server
->GetFullIP()
363 % sender
->cur_server
->GetPort() );
366 pServer
->AddFailedCount();
367 Notify_ServerRefresh( pServer
);
373 AddLogLineM(false, CFormat( _("%s (%s:%i) appears to be full.") )
374 % sender
->cur_server
->GetListName()
375 % sender
->cur_server
->GetFullIP()
376 % sender
->cur_server
->GetPort() );
379 case CS_NOTCONNECTED
:;
383 // IMPORTANT: mark this socket not to be deleted in StopConnectionTry(),
384 // because it will delete itself after this function!
385 sender
->m_bIsDeleting
= true;
387 switch (sender
->GetConnectionState()) {
389 bool autoretry
= !singleconnecting
;
391 if ((thePrefs::Reconnect()) && (autoretry
) && (!m_idRetryTimer
.IsRunning())){
392 AddLogLineM(false, wxString::Format(wxPLURAL("Automatic connection to server will retry in %d second", "Automatic connection to server will retry in %d seconds", CS_RETRYCONNECTTIME
), CS_RETRYCONNECTTIME
));
393 m_idRetryTimer
.Start(1000*CS_RETRYCONNECTTIME
);
397 case CS_DISCONNECTED
:{
398 theApp
->sharedfiles
->ClearED2KPublishInfo();
400 Notify_ServerHighlight(sender
->cur_server
,false);
401 if (connectedsocket
) {
402 connectedsocket
->Close();
404 connectedsocket
= NULL
;
405 theApp
->searchlist
->StopGlobalSearch();
406 Notify_SearchCancel();
407 theStats::GetServerConnectTimer()->StopTimer();
408 if (thePrefs::Reconnect() && !connecting
){
409 ConnectToAnyServer();
412 AddLogLineM( true, _("Connection lost") );
416 case CS_NOTCONNECTED
:{
419 AddLogLineM(false, CFormat( _("Connecting to %s (%s:%i) failed.") )
421 % sender
->cur_server
->GetFullIP()
422 % sender
->cur_server
->GetPort() );
429 if (singleconnecting
) {
430 if (pServer
&& sender
->IsServerCryptEnabledConnection() && !thePrefs::IsClientCryptLayerRequired()){
431 // try reconnecting without obfuscation
432 ConnectToServer(pServer
, false, true);
440 ServerSocketMap::iterator it
= connectionattemps
.begin();
441 while ( it
!= connectionattemps
.end() ){
442 if ( it
->second
== sender
) {
443 connectionattemps
.erase( it
);
448 TryAnotherConnectionrequest();
451 theApp
->ShowConnectionState();
454 void CServerConnect::CheckForTimeout()
456 uint32 dwCurTick
= GetTickCount();
458 ServerSocketMap::iterator it
= connectionattemps
.begin();
459 while ( it
!= connectionattemps
.end() ){
461 AddLogLineM(false, _("ERROR: Socket invalid at timeout check"));
462 connectionattemps
.erase( it
);
466 if ( dwCurTick
- it
->first
> CONSERVTIMEOUT
) {
467 uint32 key
= it
->first
;
468 CServerSocket
* value
= it
->second
;
470 if (!value
->IsSolving()) {
471 AddLogLineM(false, CFormat( _("Connection attempt to %s (%s:%i) timed out.") )
473 % value
->cur_server
->GetFullIP()
474 % value
->cur_server
->GetPort() );
476 connectionattemps
.erase( key
);
478 TryAnotherConnectionrequest();
479 DestroySocket( value
);
488 bool CServerConnect::Disconnect()
490 if (connected
&& connectedsocket
) {
491 theApp
->sharedfiles
->ClearED2KPublishInfo();
495 CServer
* update
= theApp
->serverlist
->GetServerByAddress(
496 connectedsocket
->cur_server
->GetAddress(),
497 connectedsocket
->cur_server
->GetPort());
498 Notify_ServerHighlight(update
, false);
499 theApp
->SetPublicIP(0);
500 DestroySocket(connectedsocket
);
501 connectedsocket
= NULL
;
502 theApp
->ShowConnectionState();
503 theStats::GetServerConnectTimer()->StopTimer();
511 CServerConnect::CServerConnect(CServerList
* in_serverlist
, amuleIPV4Address
&address
)
512 : m_idRetryTimer(theApp
,ID_SERVER_RETRY_TIMER_EVENT
)
514 connectedsocket
= NULL
;
515 used_list
= in_serverlist
;
516 max_simcons
= (thePrefs::IsSafeServerConnectEnabled()) ? 1 : 2;
520 singleconnecting
= false;
521 m_recurseTryAnotherConnectionrequest
= false;
523 // initalize socket for udp packets
524 if (thePrefs::GetNetworkED2K()) {
525 serverudpsocket
= new CServerUDPSocket(address
, thePrefs::GetProxyData());
527 serverudpsocket
= NULL
;
532 CServerConnect::~CServerConnect()
534 m_idRetryTimer
.Stop();
535 // stop all connections
537 // close connected socket, if any
538 DestroySocket(connectedsocket
);
539 connectedsocket
= NULL
;
541 delete serverudpsocket
;
545 CServer
* CServerConnect::GetCurrentServer()
547 if (IsConnected() && connectedsocket
) {
548 return connectedsocket
->cur_server
;
554 void CServerConnect::SetClientID(uint32 newid
)
558 if (!::IsLowID(newid
)) {
559 theApp
->SetPublicIP(newid
);
564 void CServerConnect::DestroySocket(CServerSocket
* pSck
)
569 m_lstOpenSockets
.remove(pSck
);
574 bool CServerConnect::IsLocalServer(uint32 dwIP
, uint16 nPort
)
577 if (connectedsocket
->cur_server
->GetIP() == dwIP
&& connectedsocket
->cur_server
->GetPort() == nPort
)
584 void CServerConnect::KeepConnectionAlive()
586 uint32 dwServerKeepAliveTimeout
= thePrefs::GetServerKeepAliveTimeout();
587 if (dwServerKeepAliveTimeout
&& connected
&& connectedsocket
&&
588 connectedsocket
->connectionstate
== CS_CONNECTED
&&
589 GetTickCount() - connectedsocket
->GetLastTransmission() >= dwServerKeepAliveTimeout
) {
590 // "Ping" the server if the TCP connection was not used for the specified interval with
591 // an empty publish files packet -> recommended by lugdunummaster himself!
594 files
.WriteUInt32(0); //nFiles
596 CPacket
* packet
= new CPacket(files
, OP_EDONKEYPROT
, OP_OFFERFILES
);
597 #ifdef DEBUG_CLIENT_PROTOCOL
598 AddLogLineM(true,wxT("Client: OP_OFFERFILES"));
601 // - this kind of data is highly compressable (N * (1 MD4 and at least 3 string meta data tags and 1 integer meta data tag))
602 // - the min. amount of data needed for one published file is ~100 bytes
603 // - this function is called once when connecting to a server and when a file becomes shareable - so, it's called rarely.
604 // - if the compressed size is still >= the original size, we send the uncompressed packet
605 // therefor we always try to compress the packet
606 theStats::AddUpOverheadServer(packet
->GetPacketSize());
607 connectedsocket
->SendPacket(packet
,true);
609 AddDebugLogLineM(false, logServer
, wxT("Refreshing server connection"));
613 // true if the IP is one of a server which we currently try to connect to
614 bool CServerConnect::AwaitingTestFromIP(uint32 dwIP
)
616 ServerSocketMap::iterator it
= connectionattemps
.begin();
618 while (it
!= connectionattemps
.end()) {
619 const CServerSocket
* serversocket
= it
->second
;
620 if (serversocket
&& (serversocket
->GetConnectionState() == CS_WAITFORLOGIN
) &&
621 (serversocket
->GetServerIP() == dwIP
)) {
631 bool CServerConnect::IsConnectedObfuscated() const
633 return connectedsocket
!= NULL
&& connectedsocket
->IsObfusicating();
637 void CServerConnect::OnServerHostnameResolved(void* socket
, uint32 ip
)
639 // The socket object might have been deleted while we waited for the hostname
640 // to be resolved, so we check if it's still among the open sockets.
641 SocketsList::iterator it
= std::find(m_lstOpenSockets
.begin(), m_lstOpenSockets
.end(), socket
);
642 if (it
!= m_lstOpenSockets
.end()) {
643 (*it
)->OnHostnameResolved(ip
);
645 AddLogLineNS(_("Received late result of DNS lookup, discarding."));
650 // File_checked_for_headers