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