1//
2// This file is part of the aMule Project.
3//
4// Copyright (c) 2008-2011 D��vai Tam��s ( gonosztopi@amule.org )
5// Copyright (c) 2008-2011 aMule Team ( admin@amule.org / http://www.amule.org )
6// Copyright (c) 2002-2011 Merkur ( devs@emule-project.net / http://www.emule-project.net )
7//
8// Any parts of this program derived from the xMule, lMule or eMule project,
9// or contributed by third-party developers are copyrighted by their
10// respective authors.
11//
12// This program is free software; you can redistribute it and/or modify
13// it under the terms of the GNU General Public License as published by
14// the Free Software Foundation; either version 2 of the License, or
15// (at your option) any later version.
16//
17// This program is distributed in the hope that it will be useful,
18// but WITHOUT ANY WARRANTY; without even the implied warranty of
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20// GNU General Public License for more details.
21//
22// You should have received a copy of the GNU General Public License
23// along with this program; if not, write to the Free Software
24// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
25//
26
27#include "PacketTracking.h"
28#include "../kademlia/Kademlia.h"
29#include "../../amule.h"
30#include "../../Logger.h"
31#include "../../OtherFunctions.h"
32#include "../../NetworkFunctions.h"
33#include "../../ClientList.h"
34#include "../../GetTickCount.h"
35#include <common/Macros.h>
36#include <protocol/kad/Client2Client/UDP.h>
37#include <protocol/kad2/Client2Client/UDP.h>
38
39
40using namespace Kademlia;
41
42
43CPacketTracking::~CPacketTracking()
44{
45	DeleteContents(m_mapTrackPacketsIn);
46}
47
48void CPacketTracking::AddTrackedOutPacket(uint32_t ip, uint8_t opcode)
49{
50	// this tracklist tacks _outgoing_ request packets, to make sure incoming answer packets were requested
51	// only track packets which we actually check for later
52	if (!IsTrackedOutListRequestPacket(opcode)) {
53		return;
54	}
55	uint32_t now = ::GetTickCount();
56	TrackPackets_Struct track = { ip, now, opcode };
57	listTrackedRequests.push_front(track);
58	while (!listTrackedRequests.empty()) {
59		if (now - listTrackedRequests.back().inserted > SEC2MS(180)) {
60			listTrackedRequests.pop_back();
61		} else {
62			break;
63		}
64	}
65}
66
67bool CPacketTracking::IsTrackedOutListRequestPacket(uint8_t opcode) throw()
68{
69	switch (opcode) {
70	 case KADEMLIA2_BOOTSTRAP_REQ:
71	 case KADEMLIA2_HELLO_REQ:
72	 case KADEMLIA2_HELLO_RES:
73	 case KADEMLIA2_REQ:
74	 case KADEMLIA_SEARCH_NOTES_REQ:
75	 case KADEMLIA2_SEARCH_NOTES_REQ:
76	 case KADEMLIA_PUBLISH_REQ:
77	 case KADEMLIA2_PUBLISH_KEY_REQ:
78	 case KADEMLIA2_PUBLISH_SOURCE_REQ:
79	 case KADEMLIA2_PUBLISH_NOTES_REQ:
80	 case KADEMLIA_FINDBUDDY_REQ:
81	 case KADEMLIA_CALLBACK_REQ:
82	 case KADEMLIA2_PING:
83		 return true;
84	 default:
85		 return false;
86	}
87}
88
89bool CPacketTracking::IsOnOutTrackList(uint32_t ip, uint8_t opcode, bool dontRemove)
90{
91#ifdef __DEBUG__
92	if (!IsTrackedOutListRequestPacket(opcode)) {
93		wxFAIL;	// code error / bug
94	}
95#endif
96	uint32_t now = ::GetTickCount();
97	for (TrackedPacketList::iterator it = listTrackedRequests.begin(); it != listTrackedRequests.end(); ++it) {
98		if (it->ip == ip && it->opcode == opcode && now - it->inserted < SEC2MS(180)) {
99			if (!dontRemove) {
100				listTrackedRequests.erase(it);
101			}
102			return true;
103		}
104	}
105	return false;
106}
107
108bool CPacketTracking::InTrackListIsAllowedPacket(uint32_t ip, uint8_t opcode, bool /*bValidSenderkey*/)
109{
110	// this tracklist tacks _incoming_ request packets and acts as a general flood protection by dropping
111	// too frequent requests from a single IP, avoiding response floods, processing time DOS attacks and slowing down
112	// other possible attacks/behavior (scanning indexed files, fake publish floods, etc)
113
114	// first figure out if this is a request packet to be tracked and its timelimits
115	// timelimits are chosen by estimating the max. frequency of such packets on normal operation (+ buffer)
116	// (those limits are not meant to be fine to be used by normal usage, but only supposed to be a flood detection)
117
118	uint32_t allowedPacketsPerMinute;
119	DEBUG_ONLY( const uint8_t dbgOrgOpcode = opcode; )
120
121	switch (opcode) {
122		case KADEMLIA2_BOOTSTRAP_REQ:
123			allowedPacketsPerMinute = 2;
124			break;
125		case KADEMLIA2_HELLO_REQ:
126			allowedPacketsPerMinute = 3;
127			break;
128		case KADEMLIA2_REQ:
129			allowedPacketsPerMinute = 10;
130			break;
131		case KADEMLIA2_SEARCH_NOTES_REQ:
132			allowedPacketsPerMinute = 3;
133			break;
134		case KADEMLIA2_SEARCH_KEY_REQ:
135			allowedPacketsPerMinute = 3;
136			break;
137		case KADEMLIA2_SEARCH_SOURCE_REQ:
138			allowedPacketsPerMinute = 3;
139			break;
140		case KADEMLIA2_PUBLISH_KEY_REQ:
141			allowedPacketsPerMinute = 3;
142			break;
143		case KADEMLIA2_PUBLISH_SOURCE_REQ:
144			allowedPacketsPerMinute = 2;
145			break;
146		case KADEMLIA2_PUBLISH_NOTES_REQ:
147			allowedPacketsPerMinute = 2;
148			break;
149		case KADEMLIA_FIREWALLED2_REQ:
150			opcode = KADEMLIA_FIREWALLED_REQ;
151		case KADEMLIA_FIREWALLED_REQ:
152			allowedPacketsPerMinute = 2;
153			break;
154		case KADEMLIA_FINDBUDDY_REQ:
155			allowedPacketsPerMinute = 2;
156			break;
157		case KADEMLIA_CALLBACK_REQ:
158			allowedPacketsPerMinute = 1;
159			break;
160		case KADEMLIA2_PING:
161			allowedPacketsPerMinute = 2;
162			break;
163		default:
164			// not any request packets, so it's a response packet - no further checks on this point
165			return true;
166	}
167
168	const uint32_t secondsPerPacket = 60 / allowedPacketsPerMinute;
169	const uint32_t currentTick = ::GetTickCount();
170
171	// time for cleaning up?
172	if (currentTick - lastTrackInCleanup > MIN2MS(12)) {
173		InTrackListCleanup();
174	}
175
176	// check for existing entries
177	TrackedPacketInMap::iterator it2 = m_mapTrackPacketsIn.find(ip);
178	TrackPacketsIn_Struct *trackEntry;
179	if (it2 == m_mapTrackPacketsIn.end()) {
180		trackEntry = new TrackPacketsIn_Struct();
181		trackEntry->m_ip = ip;
182		m_mapTrackPacketsIn[ip] = trackEntry;
183	} else {
184		trackEntry = it2->second;
185	}
186
187	// search specific request tracks
188	for (TrackPacketsIn_Struct::TrackedRequestList::iterator it = trackEntry->m_trackedRequests.begin(); it != trackEntry->m_trackedRequests.end(); ++it) {
189		if (it->m_opcode == opcode) {
190			// already tracked requests with this opcode, remove already expired request counts
191			if (it->m_count > 0 && currentTick - it->m_firstAdded > SEC2MS(secondsPerPacket)) {
192				uint32_t removeCount = (currentTick - it->m_firstAdded) / SEC2MS(secondsPerPacket);
193				if (removeCount > it->m_count) {
194					it->m_count = 0;
195					it->m_firstAdded = currentTick; // for the packet we just process
196				} else {
197					it->m_count -= removeCount;
198					it->m_firstAdded += SEC2MS(secondsPerPacket) * removeCount;
199				}
200			}
201			// we increase the counter in any case, even if we drop the packet later
202			it->m_count++;
203			// remember only for easier cleanup
204			trackEntry->m_lastExpire = std::max(trackEntry->m_lastExpire, it->m_firstAdded + SEC2MS(secondsPerPacket) * it->m_count);
205
206			if (CKademlia::IsRunningInLANMode() && ::IsLanIP(wxUINT32_SWAP_ALWAYS(ip))) {
207				return true;	// no flood detection in LAN mode
208			}
209
210			// now the actual check if this request is allowed
211			if (it->m_count > allowedPacketsPerMinute * 5) {
212				// this is so far above the limit that it has to be an intentional flood / misuse in any case
213				// so we take the next higher punishment and ban the IP
214				AddDebugLogLineN(logKadPacketTracking, CFormat(wxT("Massive request flood detected for opcode 0x%X (0x%X) from IP %s - Banning IP")) % opcode % dbgOrgOpcode % KadIPToString(ip));
215				theApp->clientlist->AddBannedClient(wxUINT32_SWAP_ALWAYS(ip));
216				return false; // drop packet
217			} else if (it->m_count > allowedPacketsPerMinute) {
218				// over the limit, drop the packet but do nothing else
219				if (!it->m_dbgLogged) {
220					it->m_dbgLogged = true;
221					AddDebugLogLineN(logKadPacketTracking, CFormat(wxT("Request flood detected for opcode 0x%X (0x%X) from IP %s - Dropping packets with this opcode")) % opcode % dbgOrgOpcode % KadIPToString(ip));
222				}
223				return false; // drop packet
224			} else {
225				it->m_dbgLogged = false;
226			}
227			return true;
228		}
229	}
230
231	// add a new entry for this request, no checks needed since 1 is always ok
232	TrackPacketsIn_Struct::TrackedRequestIn_Struct curTrackedRequest;
233	curTrackedRequest.m_opcode = opcode;
234	curTrackedRequest.m_dbgLogged = false;
235	curTrackedRequest.m_firstAdded = currentTick;
236	curTrackedRequest.m_count = 1;
237	// remember only for easier cleanup
238	trackEntry->m_lastExpire = std::max(trackEntry->m_lastExpire, currentTick + SEC2MS(secondsPerPacket));
239	trackEntry->m_trackedRequests.push_back(curTrackedRequest);
240	return true;
241}
242
243void CPacketTracking::InTrackListCleanup()
244{
245	const uint32_t currentTick = ::GetTickCount();
246	DEBUG_ONLY( const uint32_t dbgOldSize = m_mapTrackPacketsIn.size(); )
247	lastTrackInCleanup = currentTick;
248	for (TrackedPacketInMap::iterator it = m_mapTrackPacketsIn.begin(); it != m_mapTrackPacketsIn.end();) {
249		TrackedPacketInMap::iterator it2 = it++;
250		if (it2->second->m_lastExpire < currentTick) {
251			delete it2->second;
252			m_mapTrackPacketsIn.erase(it2);
253		}
254	}
255	AddDebugLogLineN(logKadPacketTracking, CFormat(wxT("Cleaned up Kad Incoming Requests Tracklist, entries before: %u, after %u")) % dbgOldSize % m_mapTrackPacketsIn.size());
256}
257
258void CPacketTracking::AddLegacyChallenge(const CUInt128& contactID, const CUInt128& challengeID, uint32_t ip, uint8_t opcode)
259{
260	uint32_t now = ::GetTickCount();
261	TrackChallenge_Struct sTrack = { ip, now, opcode, contactID, challengeID };
262	listChallengeRequests.push_front(sTrack);
263	while (!listChallengeRequests.empty()) {
264		if (now - listChallengeRequests.back().inserted > SEC2MS(180)) {
265			AddDebugLogLineN(logKadPacketTracking, wxT("Challenge timed out, client not verified - ") + KadIPToString(listChallengeRequests.back().ip));
266			listChallengeRequests.pop_back();
267		} else {
268			break;
269		}
270	}
271}
272
273bool CPacketTracking::IsLegacyChallenge(const CUInt128& challengeID, uint32_t ip, uint8_t opcode, CUInt128& contactID)
274{
275	uint32_t now = ::GetTickCount();
276	DEBUG_ONLY( bool warning = false; )
277	for (TrackChallengeList::iterator it = listChallengeRequests.begin(); it != listChallengeRequests.end();) {
278		TrackChallengeList::iterator it2 = it++;
279		if (it2->ip == ip && it2->opcode == opcode && now - it2->inserted < SEC2MS(180)) {
280			wxASSERT(it2->challenge != 0 || opcode == KADEMLIA2_PING);
281			if (it2->challenge == 0 || it2->challenge == challengeID) {
282				contactID = it2->contactID;
283				listChallengeRequests.erase(it2);
284				return true;
285			} else {
286				DEBUG_ONLY( warning = true; )
287			}
288		}
289	}
290#ifdef __DEBUG__
291	if (warning) {
292		AddDebugLogLineN(logKadPacketTracking, wxT("Wrong challenge answer received, client not verified (") + KadIPToString(ip) + wxT(")"));
293	}
294#endif
295	return false;
296}
297
298bool CPacketTracking::HasActiveLegacyChallenge(uint32_t ip) const
299{
300	uint32_t now = ::GetTickCount();
301	for (TrackChallengeList::const_iterator it = listChallengeRequests.begin(); it != listChallengeRequests.end(); ++it) {
302		if (it->ip == ip && now - it->inserted <= SEC2MS(180)) {
303			return true;
304		}
305	}
306	return false;
307}
308