Upstream tarball 20080414
[amule.git] / src / ServerUDPSocket.cpp
blobc46e9269b69a55c7750d134297131a3d70296840
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002 Merkur ( devs@emule-project.net / http://www.emule-project.net )
6 //
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
9 // respective authors.
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.
20 //
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
27 #include "ServerUDPSocket.h" // Interface declarations.
29 #include <protocol/Protocols.h>
30 #include <common/EventIDs.h>
31 #include <tags/ServerTags.h>
33 #include "Packet.h" // Needed for CPacket
34 #include "PartFile.h" // Needed for CPartFile
35 #include "SearchList.h" // Needed for CSearchList
36 #include "MemFile.h" // Needed for CMemFile
37 #include "DownloadQueue.h" // Needed for CDownloadQueue
38 #include "ServerList.h" // Needed for CServerList
39 #include "Server.h" // Needed for CServer
40 #include "amule.h" // Needed for theApp
41 #include "AsyncDNS.h" // Needed for CAsyncDNS
42 #include "Statistics.h" // Needed for theStats
43 #include "Logger.h"
44 #include <common/Format.h>
45 #include "updownclient.h" // Needed for SF_REMOTE_SERVER
46 #include "GuiEvents.h" // Needed for Notify_*
47 #include "Preferences.h"
48 #include "EncryptedDatagramSocket.h"
49 #include "RandomFunctions.h"
50 #include "ServerConnect.h"
53 // (TCP+3) UDP socket
56 CServerUDPSocket::CServerUDPSocket(amuleIPV4Address &address, const CProxyData *ProxyData)
57 : CMuleUDPSocket(wxT("Server UDP-Socket"), ID_SERVERUDPSOCKET_EVENT, address, ProxyData)
59 Open();
63 void CServerUDPSocket::OnPacketReceived(uint32 serverip, uint16 serverport, byte* buffer, size_t length)
65 wxCHECK_RET(length >= 2, wxT("Invalid packet."));
67 size_t nPayLoadLen = length;
68 byte* pBuffer = buffer;
69 CServer* pServer = theApp->serverlist->GetServerByIPUDP(serverip, serverport, true);
70 if (pServer && thePrefs::IsServerCryptLayerUDPEnabled() &&
71 ((pServer->GetServerKeyUDP() != 0 && pServer->SupportsObfuscationUDP()) || (pServer->GetCryptPingReplyPending() && pServer->GetChallenge() != 0)))
73 // eMule TODO
74 uint32 dwKey = 0;
75 if (pServer->GetCryptPingReplyPending() && pServer->GetChallenge() != 0 /* && pServer->GetPort() == ntohs(sockAddr.sin_port) - 12 */) {
76 dwKey = pServer->GetChallenge();
77 } else {
78 dwKey = pServer->GetServerKeyUDP();
81 wxASSERT( dwKey != 0 );
82 nPayLoadLen = CEncryptedDatagramSocket::DecryptReceivedServer(buffer, length, &pBuffer, dwKey, serverip);
83 if (nPayLoadLen == length) {
84 AddDebugLogLineM( false, logServerUDP,CFormat(wxT("Expected encrypted packet, but received unencrytped from server %s, UDPKey %u, Challenge: %u")) % pServer->GetListName() % pServer->GetServerKeyUDP() % pServer->GetChallenge());
85 } else {
86 AddDebugLogLineM( false, logServerUDP,CFormat(wxT("Received encrypted packet from server %s, UDPKey %u, Challenge: %u")) % pServer->GetListName() % pServer->GetServerKeyUDP() % pServer->GetChallenge());
90 uint8 protocol = pBuffer[0];
91 uint8 opcode = pBuffer[1];
93 if (protocol == OP_EDONKEYPROT) {
94 CMemFile data(pBuffer + 2, nPayLoadLen - 2);
95 ProcessPacket(data, opcode, serverip, serverport);
96 } else {
97 AddDebugLogLineM( false, logServerUDP,
98 wxString::Format( wxT("Received invalid packet, protocol (0x%x) and opcode (0x%x)"), protocol, opcode ));
100 theStats::AddDownOverheadOther(length);
105 void CServerUDPSocket::ProcessPacket(CMemFile& packet, uint8 opcode, uint32 ip, uint16 port)
107 CServer* update = theApp->serverlist->GetServerByIPUDP(ip, port, true);
108 unsigned size = packet.GetLength();
110 theStats::AddDownOverheadOther(size);
111 AddDebugLogLineM( false, logServerUDP,
112 CFormat( wxT("Received UDP server packet from %s:%u, opcode (0x%x)")) %
113 Uint32toStringIP(ip) % port % opcode );
115 try {
116 // Imported: OP_GLOBSEARCHRES, OP_GLOBFOUNDSOURCES & OP_GLOBSERVSTATRES
117 // This makes Server UDP Flags to be set correctly so we use less bandwith on asking servers for sources
118 // Also we process Search results and Found sources correctly now on 16.40 behaviour.
119 switch(opcode){
120 case OP_GLOBSEARCHRES: {
122 // process all search result packets
125 theApp->searchlist->ProcessUDPSearchAnswer(packet, true, ip, port - 4);
127 if (packet.GetPosition() + 2 < size) {
128 // An additional packet?
129 uint8 protocol = packet.ReadUInt8();
130 uint8 new_opcode = packet.ReadUInt8();
132 if (protocol != OP_EDONKEYPROT || new_opcode != OP_GLOBSEARCHRES) {
133 AddDebugLogLineM( true, logServerUDP,
134 wxT("Server search reply got additional bogus bytes.") );
135 break;
136 } else {
137 AddDebugLogLineM( true, logServerUDP,
138 wxT("Got server search reply with additional packet.") );
142 } while (packet.GetPosition()+2 < size);
144 break;
146 case OP_GLOBFOUNDSOURCES:{
147 // process all source packets
148 do {
149 CMD4Hash fileid = packet.ReadHash();
150 if (CPartFile* file = theApp->downloadqueue->GetFileByID(fileid)) {
151 file->AddSources(packet, ip, port-4, SF_REMOTE_SERVER, false);
152 } else {
153 AddDebugLogLineM( true, logServerUDP, wxT("Sources received for unknown file") );
154 // skip sources for that file
155 uint8 count = packet.ReadUInt8();
156 packet.Seek(count*(4+2), wxFromCurrent);
159 if (packet.GetPosition()+2 < size) {
160 // An additional packet?
161 uint8 protocol = packet.ReadUInt8();
162 uint8 new_opcode = packet.ReadUInt8();
164 if (protocol != OP_EDONKEYPROT || new_opcode != OP_GLOBFOUNDSOURCES) {
165 AddDebugLogLineM( true, logServerUDP,
166 wxT("Server sources reply got additional bogus bytes.") );
167 break;
170 } while ((packet.GetPosition() + 2) < size);
171 break;
174 case OP_GLOBSERVSTATRES:{
175 // Reviewed with 0.47c
176 if (!update) {
177 throw wxString(wxT("Unknown server on a OP_GLOBSERVSTATRES packet (") + Uint32toStringIP(ip) + wxString::Format(wxT(":%d)"), port-4));
179 if( size < 12) {
180 throw(wxString(wxString::Format(wxT("Invalid OP_GLOBSERVSTATRES packet (size=%u)"),size)));
182 uint32 challenge = packet.ReadUInt32();
183 if (challenge != update->GetChallenge()) {
184 throw(wxString(wxString::Format(wxT("Invalid challenge on OP_GLOBSERVSTATRES packet (0x%x != 0x%x)"),challenge,update->GetChallenge())));
187 update->SetChallenge(0);
188 update->SetCryptPingReplyPending(false);
189 uint32 tNow = ::GetTickCount();
190 update->SetLastPingedTime(tNow - (rand() % HR2S(1))); // if we used Obfuscated ping, we still need to reset the time properly
192 uint32 cur_user = packet.ReadUInt32();
193 uint32 cur_files = packet.ReadUInt32();
194 uint32 cur_maxusers = 0;
195 uint32 cur_softfiles = 0;
196 uint32 cur_hardfiles = 0;
197 uint32 uUDPFlags = 0;
198 uint32 uLowIDUsers = 0;
199 uint32 dwServerUDPKey = 0;
200 uint16 nTCPObfuscationPort = 0;
201 uint16 nUDPObfuscationPort = 0;
202 if( size >= 16 ){
203 cur_maxusers = packet.ReadUInt32();
204 if( size >= 24 ){
205 cur_softfiles = packet.ReadUInt32();
206 cur_hardfiles = packet.ReadUInt32();
207 if( size >= 28 ){
208 uUDPFlags = packet.ReadUInt32();
209 if( size >= 32 ){
210 uLowIDUsers = packet.ReadUInt32();
211 if (size >= 40) {
212 nUDPObfuscationPort = packet.ReadUInt16();
213 nTCPObfuscationPort = packet.ReadUInt16();
214 dwServerUDPKey = packet.ReadUInt32();
221 update->SetPing( ::GetTickCount() - update->GetLastPinged() );
222 update->SetUserCount( cur_user );
223 update->SetFileCount( cur_files );
224 update->SetMaxUsers( cur_maxusers );
225 update->SetSoftFiles( cur_softfiles );
226 update->SetHardFiles( cur_hardfiles );
227 update->SetUDPFlags( uUDPFlags );
228 update->SetLowIDUsers( uLowIDUsers );
229 update->SetServerKeyUDP(dwServerUDPKey);
230 update->SetObfuscationPortTCP(nTCPObfuscationPort);
231 update->SetObfuscationPortUDP(nUDPObfuscationPort);
233 Notify_ServerRefresh( update );
235 update->SetLastDescPingedCount(false);
236 if (update->GetLastDescPingedCount() < 2) {
237 // eserver 16.45+ supports a new OP_SERVER_DESC_RES answer, if the OP_SERVER_DESC_REQ contains a uint32
238 // challenge, the server returns additional info with OP_SERVER_DESC_RES. To properly distinguish the
239 // old and new OP_SERVER_DESC_RES answer, the challenge has to be selected carefully. The first 2 bytes
240 // of the challenge (in network byte order) MUST NOT be a valid string-len-int16!
241 CPacket* sendpacket = new CPacket(OP_SERVER_DESC_REQ, 4, OP_EDONKEYPROT);
242 uint32 uDescReqChallenge = ((uint32)GetRandomUint16() << 16) + INV_SERV_DESC_LEN; // 0xF0FF = an 'invalid' string length.
243 update->SetDescReqChallenge(uDescReqChallenge);
244 sendpacket->CopyUInt32ToDataBuffer(uDescReqChallenge);
245 //theStats.AddUpDataOverheadServer(packet->size);
246 AddDebugLogLineM(false, logServerUDP, CFormat(wxT(">>> Sending OP__ServDescReq to server %s:%u, challenge %08x\n")) % update->GetAddress() % update->GetPort() % uDescReqChallenge);
247 theApp->serverconnect->SendUDPPacket(sendpacket, update, true);
248 } else {
249 update->SetLastDescPingedCount(true);
252 theApp->ShowUserCount();
253 break;
255 case OP_SERVER_DESC_RES:{
256 // Reviewed with 0.47c
257 if (!update) {
258 throw(wxString(wxT("Received OP_SERVER_DESC_RES from an unknown server")));
261 // old packet: <name_len 2><name name_len><desc_len 2 desc_en>
262 // new packet: <challenge 4><taglist>
264 // NOTE: To properly distinguish between the two packets which are both useing the same opcode...
265 // the first two bytes of <challenge> (in network byte order) have to be an invalid <name_len> at least.
267 uint16 Len = packet.ReadUInt16();
269 packet.Seek(-2, wxFromCurrent); // Step back
271 if (size >= 8 && Len == INV_SERV_DESC_LEN) {
273 if (update->GetDescReqChallenge() != 0 && packet.ReadUInt32() == update->GetDescReqChallenge()) {
275 update->SetDescReqChallenge(0);
277 uint32 uTags = packet.ReadUInt32();
278 for (uint32 i = 0; i < uTags; ++i) {
279 CTag tag(packet, update->GetUnicodeSupport());
280 switch (tag.GetNameID()) {
281 case ST_SERVERNAME:
282 update->SetListName(tag.GetStr());
283 break;
284 case ST_DESCRIPTION:
285 update->SetDescription(tag.GetStr());
286 break;
287 case ST_DYNIP:
288 update->SetDynIP(tag.GetStr());
289 break;
290 case ST_VERSION:
291 if (tag.IsStr()) {
292 update->SetVersion(tag.GetStr());
293 } else if (tag.IsInt()) {
294 wxString strVersion = wxString::Format(wxT("%u.%u"), tag.GetInt() >> 16, tag.GetInt() & 0xFFFF);
295 update->SetVersion(strVersion);
297 break;
298 case ST_AUXPORTSLIST:
299 update->SetAuxPortsList(tag.GetStr());
300 break;
301 default:
302 // Unknown tag
306 } else {
307 // A server sent us a new server description packet (including a challenge) although we did not
308 // ask for it. This may happen, if there are multiple servers running on the same machine with
309 // multiple IPs. If such a server is asked for a description, the server will answer 2 times,
310 // but with the same IP.
311 // ignore this packet
314 } else {
315 update->SetDescription(packet.ReadString(update->GetUnicodeSupport()));
316 update->SetListName(packet.ReadString(update->GetUnicodeSupport()));
318 break;
320 default:
321 AddDebugLogLineM( true, logServerUDP,
322 wxString::Format( wxT("Unknown Server UDP opcode %x"), opcode ) );
324 } catch (const wxString& error) {
325 AddDebugLogLineM(false, logServerUDP, wxT("Error while processing incoming UDP Packet: ") + error);
326 } catch (const CInvalidPacket& error) {
327 AddDebugLogLineM(false, logServerUDP, wxT("Invalid UDP packet encountered: ") + error.what());
328 } catch (const CEOFException& e) {
329 AddDebugLogLineM(false, logServerUDP, wxT("IO error while processing incoming UDP Packet: ") + e.what());
332 if (update) {
333 update->ResetFailedCount();
334 Notify_ServerRefresh( update );
339 void CServerUDPSocket::OnReceiveError(int errorCode, uint32 ip, uint16 port)
341 CMuleUDPSocket::OnReceiveError(errorCode, ip, port);
343 // If we are not currently pinging this server, increase the failure counter
344 CServer* pServer = theApp->serverlist->GetServerByIPUDP(ip, port, true);
345 if (pServer && !pServer->GetCryptPingReplyPending() && GetTickCount() - pServer->GetLastPinged() >= SEC2MS(30)) {
346 pServer->AddFailedCount();
347 Notify_ServerRefresh(pServer);
352 void CServerUDPSocket::SendPacket(CPacket* packet, CServer* host, bool delPacket, bool rawpacket, uint16 port_offset)
354 ServerUDPPacket item = { NULL, 0, 0, wxEmptyString };
356 if (host->HasDynIP()) {
357 item.addr = host->GetDynIP();
358 } else {
359 item.ip = host->GetIP();
362 // 4 (default) for standard sending, 12 for obfuscated ping, that's all for now.
363 // Might be changed if encrypted bellow, so don't move it.
364 item.port = host->GetPort() + port_offset;
366 // We might need to encrypt the packet for this server.
367 if (!rawpacket && thePrefs::IsServerCryptLayerUDPEnabled() && host->GetServerKeyUDP() != 0 && host->SupportsObfuscationUDP()) {
368 uint16 uRawPacketSize = packet->GetPacketSize() + 2;
369 byte* pRawPacket = new byte[uRawPacketSize];
370 memcpy(pRawPacket, packet->GetUDPHeader(), 2);
371 memcpy(pRawPacket + 2, packet->GetDataBuffer(), packet->GetPacketSize());
373 uRawPacketSize = CEncryptedDatagramSocket::EncryptSendServer(&pRawPacket, uRawPacketSize, host->GetServerKeyUDP());
374 AddDebugLogLineM(false, logServerUDP, CFormat(wxT("Sending encrypted packet to server %s, UDPKey %u, port %u, original OPCode 0x%02x")) % host->GetListName() % host->GetServerKeyUDP() % host->GetObfuscationPortUDP() % packet->GetOpCode());
375 item.port = host->GetObfuscationPortUDP();
377 CMemFile encryptedpacket(pRawPacket + 2, uRawPacketSize - 2);
378 item.packet = new CPacket(encryptedpacket, pRawPacket[0], pRawPacket[1]);
380 if (delPacket) {
381 delete packet;
384 } else {
385 AddDebugLogLineM(false, logServerUDP, CFormat(wxT("Sending regular packet to server %s, port %u (raw = %s), OPCode 0x%02x")) % host->GetListName() % host->GetObfuscationPortUDP() % (rawpacket ? wxT("True") : wxT("False")) % packet->GetOpCode());
386 if (delPacket) {
387 item.packet = packet;
388 } else {
389 item.packet = new CPacket(*packet);
394 m_queue.push_back(item);
396 // If there is more than one item in the queue,
397 // then we are already waiting for a dns query.
398 if (m_queue.size() == 1) {
399 SendQueue();
404 void CServerUDPSocket::SendQueue()
406 while (m_queue.size()) {
407 ServerUDPPacket item = m_queue.front();
408 CPacket* packet = item.packet;
410 // Do we need to do a DNS lookup before sending?
411 wxASSERT(item.ip || !item.addr.IsEmpty());
412 if (!item.addr.IsEmpty()) {
413 // This not an ip but a hostname. Resolve it.
414 CServer* update = theApp->serverlist->GetServerByAddress(item.addr, item.port);
415 if (update) {
416 if (update->GetLastDNSSolve() + DNS_SOLVE_TIME < ::GetTickCount64()) {
417 // Its time for a new check.
418 CAsyncDNS* dns = new CAsyncDNS(item.addr, DNS_UDP, theApp, this);
419 if ((dns->Create() != wxTHREAD_NO_ERROR) || (dns->Run() != wxTHREAD_NO_ERROR)) {
420 // Not much we can do here, just drop the packet.
421 m_queue.pop_front();
422 continue;
424 update->SetDNSError(false);
425 update->SetLastDNSSolve(::GetTickCount64());
426 // Wait for the DNS query to be resolved
427 return;
428 } else {
429 // It has been checked recently, don't re-check yet.
430 if (update->GetDNSError()) {
431 // Drop the packet, dns failed last time
432 AddDebugLogLineM(false, logServerUDP, wxT("Trying to send an UDP packet to a server host that failed DNS: ")+item.addr);
433 m_queue.pop_front();
434 continue;
435 } else {
436 // It has been solved or is solving.
437 if (update->GetIP()) {
438 // It has been solved and ok
439 AddDebugLogLineM(false, logServerUDP, wxT("Sending a UDP packet to a resolved DNS server host: ")+item.addr);
440 // Update the item IP
441 item.ip = update->GetIP();
442 // It'll fallback to the sending.
443 } else {
444 AddDebugLogLineM(false, logServerUDP, wxT("Trying to send an UDP packet to a server host that is checking DNS: ")+item.addr);
445 // Let the packet queued, and wait for resolution
446 // Meanwhile, send other packets.
447 m_queue.pop_front();
448 m_queue.push_back(item);
449 return;
453 } else {
454 AddDebugLogLineM(false, logServerUDP, wxT("Trying to send an UDP packet to a server host that is not on serverlist"));
455 // Not much we can do here, just drop the packet.
456 m_queue.pop_front();
457 continue;
461 CServer* update = theApp->serverlist->GetServerByIPUDP(item.ip, item.port, true);
462 if (update) {
463 AddDebugLogLineM(false, logServerUDP, wxT("Sending an UDP packet to a server: ")+update->GetAddress());
464 // Don't encrypt, as this is already either encrypted or refused to encrypt.
465 CMuleUDPSocket::SendPacket(packet, item.ip, item.port, false, NULL, false, 0);
466 } else {
467 AddDebugLogLineM(false, logServerUDP, wxT("Sending an UDP packet to a server no in serverlist: ")+Uint32_16toStringIP_Port(item.ip,item.port));
470 m_queue.pop_front();
475 void CServerUDPSocket::OnHostnameResolved(uint32 ip)
477 wxCHECK_RET(m_queue.size(), wxT("DNS query returned, but no packets are queued."));
479 ServerUDPPacket item = m_queue.front();
480 wxCHECK_RET(!item.ip && !item.addr.IsEmpty(), wxT("DNS resolution not expected."));
482 /* An asynchronous database routine completed. */
483 CServer* update = theApp->serverlist->GetServerByAddress(item.addr, item.port);
484 if (ip == 0) {
485 update->SetDNSError(true);
486 m_queue.pop_front();
487 } else {
488 if (update) {
489 update->SetID(ip);
492 item.addr.Clear();
493 item.ip = ip;
496 SendQueue();
498 // File_checked_for_headers