1//
2// This file is part of the aMule Project.
3//
4// Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
5// Copyright (c) 2002-2011 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.
10//
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.
15//
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
24//
25
26
27#include "ClientUDPSocket.h"	// Interface declarations
28
29#include <protocol/Protocols.h>
30#include <protocol/ed2k/Client2Client/TCP.h> // Sometimes we reply with TCP packets.
31#include <protocol/ed2k/Client2Client/UDP.h>
32#include <protocol/kad2/Client2Client/UDP.h>
33#include <common/EventIDs.h>
34#include <common/Format.h>	// Needed for CFormat
35
36#include "Preferences.h"		// Needed for CPreferences
37#include "PartFile.h"			// Needed for CPartFile
38#include "updownclient.h"		// Needed for CUpDownClient
39#include "UploadQueue.h"		// Needed for CUploadQueue
40#include "Packet.h"				// Needed for CPacket
41#include "SharedFileList.h"		// Needed for CSharedFileList
42#include "DownloadQueue.h"		// Needed for CDownloadQueue
43#include "Statistics.h"			// Needed for theStats
44#include "amule.h"				// Needed for theApp
45#include "ClientList.h"			// Needed for clientlist (buddy support)
46#include "ClientTCPSocket.h"	// Needed for CClientTCPSocket
47#include "MemFile.h"			// Needed for CMemFile
48#include "Logger.h"
49#include "kademlia/kademlia/Kademlia.h"
50#include "kademlia/utils/KadUDPKey.h"
51#include "zlib.h"
52#include "EncryptedDatagramSocket.h"
53
54//
55// CClientUDPSocket -- Extended eMule UDP socket
56//
57
58CClientUDPSocket::CClientUDPSocket(const amuleIPV4Address& address, const CProxyData* ProxyData)
59	: CMuleUDPSocket(wxT("Client UDP-Socket"), ID_CLIENTUDPSOCKET_EVENT, address, ProxyData)
60{
61	if (!thePrefs::IsUDPDisabled()) {
62		Open();
63	}
64}
65
66
67void CClientUDPSocket::OnReceive(int errorCode)
68{
69	CMuleUDPSocket::OnReceive(errorCode);
70
71	// TODO: A better solution is needed.
72	if (thePrefs::IsUDPDisabled()) {
73		Close();
74	}
75}
76
77
78void CClientUDPSocket::OnPacketReceived(uint32 ip, uint16 port, byte* buffer, size_t length)
79{
80	wxCHECK_RET(length >= 2, wxT("Invalid packet."));
81
82	uint8_t *decryptedBuffer;
83	uint32_t receiverVerifyKey;
84	uint32_t senderVerifyKey;
85	int packetLen = CEncryptedDatagramSocket::DecryptReceivedClient(buffer, length, &decryptedBuffer, ip, &receiverVerifyKey, &senderVerifyKey);
86
87	uint8_t protocol = decryptedBuffer[0];
88	uint8_t opcode	 = decryptedBuffer[1];
89
90	if (packetLen >= 1) {
91		try {
92			switch (protocol) {
93				case OP_EMULEPROT:
94					ProcessPacket(decryptedBuffer + 2, packetLen - 2, opcode, ip, port);
95					break;
96
97				case OP_KADEMLIAHEADER:
98					theStats::AddDownOverheadKad(length);
99					if (packetLen >= 2) {
100						Kademlia::CKademlia::ProcessPacket(decryptedBuffer, packetLen, wxUINT32_SWAP_ALWAYS(ip), port, (Kademlia::CPrefs::GetUDPVerifyKey(ip) == receiverVerifyKey), Kademlia::CKadUDPKey(senderVerifyKey, theApp->GetPublicIP(false)));
101					} else {
102						throw wxString(wxT("Kad packet too short"));
103					}
104					break;
105
106				case OP_KADEMLIAPACKEDPROT:
107					theStats::AddDownOverheadKad(length);
108					if (packetLen >= 2) {
109						uint32_t newSize = packetLen * 10 + 300; // Should be enough...
110						std::vector<uint8_t> unpack(newSize);
111						uLongf unpackedsize = newSize - 2;
112						uint16_t result = uncompress(&(unpack[2]), &unpackedsize, decryptedBuffer + 2, packetLen - 2);
113						if (result == Z_OK) {
114							AddDebugLogLineN(logClientKadUDP, wxT("Correctly uncompressed Kademlia packet"));
115							unpack[0] = OP_KADEMLIAHEADER;
116							unpack[1] = opcode;
117							Kademlia::CKademlia::ProcessPacket(&(unpack[0]), unpackedsize + 2, wxUINT32_SWAP_ALWAYS(ip), port, (Kademlia::CPrefs::GetUDPVerifyKey(ip) == receiverVerifyKey), Kademlia::CKadUDPKey(senderVerifyKey, theApp->GetPublicIP(false)));
118						} else {
119							AddDebugLogLineN(logClientKadUDP, wxT("Failed to uncompress Kademlia packet"));
120						}
121					} else {
122						throw wxString(wxT("Kad packet (compressed) too short"));
123					}
124					break;
125
126				default:
127					AddDebugLogLineN(logClientUDP, CFormat(wxT("Unknown opcode on received packet: 0x%x")) % protocol);
128			}
129		} catch (const wxString& DEBUG_ONLY(e)) {
130			AddDebugLogLineN(logClientUDP, wxT("Error while parsing UDP packet: ") + e);
131		} catch (const CInvalidPacket& DEBUG_ONLY(e)) {
132			AddDebugLogLineN(logClientUDP, wxT("Invalid UDP packet encountered: ") + e.what());
133		} catch (const CEOFException& DEBUG_ONLY(e)) {
134			AddDebugLogLineN(logClientUDP, wxT("Malformed packet encountered while parsing UDP packet: ") + e.what());
135		}
136	}
137}
138
139
140void CClientUDPSocket::ProcessPacket(byte* packet, int16 size, int8 opcode, uint32 host, uint16 port)
141{
142	switch (opcode) {
143		case OP_REASKCALLBACKUDP: {
144			AddDebugLogLineN( logClientUDP, wxT("Client UDP socket; OP_REASKCALLBACKUDP") );
145			theStats::AddDownOverheadOther(size);
146			CUpDownClient* buddy = theApp->clientlist->GetBuddy();
147			if( buddy ) {
148				if( size < 17 || buddy->GetSocket() == NULL ) {
149					break;
150				}
151				if (!md4cmp(packet, buddy->GetBuddyID())) {
152					/*
153						The packet has an initial 16 bytes key for the buddy.
154						This is currently unused, so to make the transformation
155						we discard the first 10 bytes below and then overwrite
156						the other 6 with ip/port.
157					*/
158					CMemFile mem_packet(packet+10,size-10);
159					// Change the ip and port while leaving the rest untouched
160					mem_packet.Seek(0,wxFromStart);
161					mem_packet.WriteUInt32(host);
162					mem_packet.WriteUInt16(port);
163					CPacket* response = new CPacket(mem_packet, OP_EMULEPROT, OP_REASKCALLBACKTCP);
164					AddDebugLogLineN( logClientUDP, wxT("Client UDP socket: send OP_REASKCALLBACKTCP") );
165					theStats::AddUpOverheadFileRequest(response->GetPacketSize());
166					buddy->GetSocket()->SendPacket(response);
167				}
168			}
169			break;
170		}
171		case OP_REASKFILEPING: {
172			AddDebugLogLineN( logClientUDP, wxT("Client UDP socket: OP_REASKFILEPING") );
173			theStats::AddDownOverheadFileRequest(size);
174
175			CMemFile data_in(packet, size);
176			CMD4Hash reqfilehash = data_in.ReadHash();
177			CKnownFile* reqfile = theApp->sharedfiles->GetFileByID(reqfilehash);
178			bool bSenderMultipleIpUnknown = false;
179			CUpDownClient* sender = theApp->uploadqueue->GetWaitingClientByIP_UDP(host, port, true, &bSenderMultipleIpUnknown);
180
181			if (!reqfile) {
182				CPacket* response = new CPacket(OP_FILENOTFOUND,0,OP_EMULEPROT);
183				theStats::AddUpOverheadFileRequest(response->GetPacketSize());
184				if (sender) {
185					SendPacket(response, host, port, sender->ShouldReceiveCryptUDPPackets(), sender->GetUserHash().GetHash(), false, 0);
186				} else {
187					SendPacket(response, host, port, false, NULL, false, 0);
188				}
189
190				break;
191			}
192
193			if (sender){
194				sender->CheckForAggressive();
195
196				//Make sure we are still thinking about the same file
197				if (reqfilehash == sender->GetUploadFileID()) {
198					sender->AddAskedCount();
199					sender->SetUDPPort(port);
200					sender->SetLastUpRequest();
201
202					if (sender->GetUDPVersion() > 3) {
203						sender->ProcessExtendedInfo(&data_in, reqfile);
204					} else  if (sender->GetUDPVersion() > 2) {
205						uint16 nCompleteCountLast = sender->GetUpCompleteSourcesCount();
206						uint16 nCompleteCountNew = data_in.ReadUInt16();
207						sender->SetUpCompleteSourcesCount(nCompleteCountNew);
208						if (nCompleteCountLast != nCompleteCountNew) {
209							reqfile->UpdatePartsInfo();
210						}
211					}
212
213					CMemFile data_out(128);
214					if(sender->GetUDPVersion() > 3) {
215						if (reqfile->IsPartFile()) {
216							((CPartFile*)reqfile)->WritePartStatus(&data_out);
217						} else {
218							data_out.WriteUInt16(0);
219						}
220					}
221
222					data_out.WriteUInt16(sender->GetUploadQueueWaitingPosition());
223					CPacket* response = new CPacket(data_out, OP_EMULEPROT, OP_REASKACK);
224					theStats::AddUpOverheadFileRequest(response->GetPacketSize());
225					AddDebugLogLineN( logClientUDP, wxT("Client UDP socket: OP_REASKACK to ") + sender->GetFullIP());
226					SendPacket(response, host, port, sender->ShouldReceiveCryptUDPPackets(), sender->GetUserHash().GetHash(), false, 0);
227				} else {
228					AddDebugLogLineN( logClientUDP, wxT("Client UDP socket; ReaskFilePing; reqfile does not match") );
229				}
230			} else {
231				if (!bSenderMultipleIpUnknown) {
232					if ((theStats::GetWaitingUserCount() + 50) > thePrefs::GetQueueSize()) {
233						CPacket* response = new CPacket(OP_QUEUEFULL,0,OP_EMULEPROT);
234						theStats::AddUpOverheadFileRequest(response->GetPacketSize());
235						SendPacket(response,host,port, false, NULL, false, 0); // we cannot answer this one encrypted since we dont know this client
236					}
237				} else {
238					AddDebugLogLineN(logClientUDP, CFormat(wxT("UDP Packet received - multiple clients with the same IP but different UDP port found. Possible UDP Portmapping problem, enforcing TCP connection. IP: %s, Port: %u")) % Uint32toStringIP(host) % port);
239				}
240			}
241			break;
242		}
243		case OP_QUEUEFULL: {
244			AddDebugLogLineN( logClientUDP, wxT("Client UDP socket: OP_QUEUEFULL") );
245			theStats::AddDownOverheadOther(size);
246			CUpDownClient* sender = theApp->downloadqueue->GetDownloadClientByIP_UDP(host,port);
247			if (sender) {
248				sender->SetRemoteQueueFull(true);
249				sender->UDPReaskACK(0);
250			}
251			break;
252		}
253		case OP_REASKACK: {
254			theStats::AddDownOverheadFileRequest(size);
255			CUpDownClient* sender = theApp->downloadqueue->GetDownloadClientByIP_UDP(host,port);
256			if (sender) {
257				CMemFile data_in(packet,size);
258				if ( sender->GetUDPVersion() > 3 ) {
259					sender->ProcessFileStatus(true, &data_in, sender->GetRequestFile());
260				}
261				uint16 nRank = data_in.ReadUInt16();
262				sender->SetRemoteQueueFull(false);
263				sender->UDPReaskACK(nRank);
264			}
265			break;
266		}
267		case OP_FILENOTFOUND: {
268			AddDebugLogLineN( logClientUDP, wxT("Client UDP socket: OP_FILENOTFOUND") );
269			theStats::AddDownOverheadFileRequest(size);
270			CUpDownClient* sender = theApp->downloadqueue->GetDownloadClientByIP_UDP(host,port);
271			if (sender){
272				sender->UDPReaskFNF(); // may delete 'sender'!
273				sender = NULL;
274			}
275			break;
276		}
277		case OP_DIRECTCALLBACKREQ:
278		{
279			AddDebugLogLineN( logClientUDP, wxT("Client UDP socket: OP_DIRECTCALLBACKREQ") );
280			theStats::AddDownOverheadOther(size);
281			if (!theApp->clientlist->AllowCallbackRequest(host)) {
282				AddDebugLogLineN(logClientUDP, wxT("Ignored DirectCallback Request because this IP (") + Uint32toStringIP(host) + wxT(") has sent too many requests within a short time"));
283				break;
284			}
285			// do we accept callbackrequests at all?
286			if (Kademlia::CKademlia::IsRunning() && Kademlia::CKademlia::IsFirewalled()) {
287				theApp->clientlist->AddTrackCallbackRequests(host);
288				CMemFile data(packet, size);
289				uint16_t remoteTCPPort = data.ReadUInt16();
290				CMD4Hash userHash(data.ReadHash());
291				uint8_t connectOptions = data.ReadUInt8();
292				CUpDownClient* requester = NULL;
293				CClientList::SourceList clients = theApp->clientlist->GetClientsByHash(userHash);
294				for (CClientList::SourceList::iterator it = clients.begin(); it != clients.end(); ++it) {
295					if ((host == 0 || it->GetIP() == host) && (remoteTCPPort == 0 || it->GetUserPort() == remoteTCPPort)) {
296						requester = it->GetClient();
297						break;
298					}
299				}
300				if (requester == NULL) {
301					requester = new CUpDownClient(remoteTCPPort, host, 0, 0, NULL, true, true);
302					requester->SetUserHash(CMD4Hash(userHash));
303					theApp->clientlist->AddClient(requester);
304				}
305				requester->SetConnectOptions(connectOptions, true, false);
306				requester->SetDirectUDPCallbackSupport(false);
307				requester->SetIP(host);
308				requester->SetUserPort(remoteTCPPort);
309				AddDebugLogLineN(logClientUDP, wxT("Accepting incoming DirectCallback Request from ") + Uint32toStringIP(host));
310				requester->TryToConnect();
311			} else {
312				AddDebugLogLineN(logClientUDP, wxT("Ignored DirectCallback Request because we do not accept Direct Callbacks at all (") + Uint32toStringIP(host) + wxT(")"));
313			}
314			break;
315		}
316		default:
317			theStats::AddDownOverheadOther(size);
318	}
319}
320// File_checked_for_headers
321