Fix issue #1391: 'Connect at startup and the connect button'
[amule.git] / src / IPFilter.cpp
blob4b042bbc8ba92faa243ac294cdef030ea6bb6b93
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-2008 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
26 #include <wx/stdpaths.h> // Needed for GetDataDir
27 #include <wx/ffile.h>
29 #include "IPFilter.h" // Interface declarations.
30 #include "IPFilterScanner.h" // Interface for flexer
31 #include "Preferences.h" // Needed for thePrefs
32 #include "amule.h" // Needed for theApp
33 #include "Statistics.h" // Needed for theStats
34 #include "HTTPDownload.h" // Needed for CHTTPDownloadThread
35 #include "Logger.h" // Needed for AddDebugLogLineM
36 #include <common/Format.h> // Needed for CFormat
37 #include <common/StringFunctions.h> // Needed for CSimpleTokenizer
38 #include <common/FileFunctions.h> // Needed for UnpackArchive
39 #include <common/TextFile.h> // Needed for CTextFile
40 #include "ThreadScheduler.h" // Needed for CThreadScheduler and CThreadTask
41 #include "ClientList.h" // Needed for CClientList
42 #include "ServerList.h" // Needed for CServerList
43 #include <common/Macros.h> // Needed for DEBUG_ONLY()
44 #include "RangeMap.h" // Needed for CRangeMap
45 #include "ServerConnect.h" // Needed for ConnectToAnyServer()
46 #include "DownloadQueue.h" // Needed for theApp->downloadqueue
49 ////////////////////////////////////////////////////////////
50 // CIPFilterEvent
52 BEGIN_DECLARE_EVENT_TYPES()
53 DECLARE_EVENT_TYPE(MULE_EVT_IPFILTER_LOADED, -1)
54 END_DECLARE_EVENT_TYPES()
56 DEFINE_EVENT_TYPE(MULE_EVT_IPFILTER_LOADED)
59 class CIPFilterEvent : public wxEvent
61 public:
62 CIPFilterEvent(CIPFilter::RangeIPs rangeIPs, CIPFilter::RangeLengths rangeLengths, CIPFilter::RangeNames rangeNames)
63 : wxEvent(-1, MULE_EVT_IPFILTER_LOADED)
65 // Physically copy the vectors, this will hopefully resize them back to their needed capacity.
66 m_rangeIPs = rangeIPs;
67 m_rangeLengths = rangeLengths;
68 // This one is usually empty, and should always be swapped, not copied.
69 std::swap(m_rangeNames, rangeNames);
72 /** @see wxEvent::Clone */
73 virtual wxEvent* Clone() const {
74 return new CIPFilterEvent(*this);
77 CIPFilter::RangeIPs m_rangeIPs;
78 CIPFilter::RangeLengths m_rangeLengths;
79 CIPFilter::RangeNames m_rangeNames;
83 typedef void (wxEvtHandler::*MuleIPFilterEventFunction)(CIPFilterEvent&);
85 //! Event-handler for completed hashings of new shared files and partfiles.
86 #define EVT_MULE_IPFILTER_LOADED(func) \
87 DECLARE_EVENT_TABLE_ENTRY(MULE_EVT_IPFILTER_LOADED, -1, -1, \
88 (wxObjectEventFunction) (wxEventFunction) \
89 wxStaticCastEvent(MuleIPFilterEventFunction, &func), (wxObject*) NULL),
92 ////////////////////////////////////////////////////////////
93 // Thread task for loading the ipfilter.dat files.
95 /**
96 * This task loads the two ipfilter.dat files, a task that
97 * can take quite a while on a slow system with a large dat-
98 * file.
100 class CIPFilterTask : public CThreadTask
102 public:
103 CIPFilterTask(wxEvtHandler* owner)
104 : CThreadTask(wxT("Load IPFilter"), wxEmptyString, ETP_Critical),
105 m_storeDescriptions(false),
106 m_owner(owner)
110 private:
111 void Entry()
113 AddLogLineN(_("Loading IP filters 'ipfilter.dat' and 'ipfilter_static.dat'."));
114 if ( !LoadFromFile(theApp->ConfigDir + wxT("ipfilter.dat")) &&
115 thePrefs::UseIPFilterSystem() ) {
116 // Load from system wide IP filter file
117 wxStandardPathsBase &spb(wxStandardPaths::Get());
118 #ifdef __WXMSW__
119 wxString dataDir(spb.GetPluginsDir());
120 #elif defined(__WXMAC__)
121 wxString dataDir(spb.GetDataDir());
122 #else
123 wxString dataDir(spb.GetDataDir().BeforeLast(wxT('/')) + wxT("/amule"));
124 #endif
125 wxString systemwideFile(JoinPaths(dataDir,wxT("ipfilter.dat")));
126 LoadFromFile(systemwideFile);
130 LoadFromFile(theApp->ConfigDir + wxT("ipfilter_static.dat"));
132 uint8 accessLevel = thePrefs::GetIPFilterLevel();
133 uint32 size = m_result.size();
134 // Reserve a little more so we don't have to resize the vector later.
135 // (Map ranges can exist that have to be stored in several parts.)
136 // Extra memory will be freed in the end.
137 m_rangeIPs.reserve(size + 1000);
138 m_rangeLengths.reserve(size + 1000);
139 if (m_storeDescriptions) {
140 m_rangeNames.reserve(size + 1000);
142 for (IPMap::iterator it = m_result.begin(); it != m_result.end(); ++it) {
143 if (it->AccessLevel < accessLevel) {
144 // Calculate range "length"
145 // (which is included-end - start and thus length - 1)
146 // Encoding:
147 // 0 - 0x7fff same
148 // 0x8000 - 0xffff 0xfff - 0x07ffffff
149 // that means: remove msb, shift left by 12 bit, add 0xfff
150 // so it can cover 8 consecutive class A nets
151 // larger ranges (or theoretical ranges with uneven ends) have to be split
152 uint32 startIP = it.keyStart();
153 uint32 realLength = it.keyEnd() - it.keyStart() + 1;
154 std::string * descp = 0;
155 while (realLength) {
156 m_rangeIPs.push_back(startIP);
157 uint32 curLength = realLength;
158 uint16 pushLength;
159 if (realLength <= 0x8000) {
160 pushLength = realLength - 1;
161 } else {
162 if (curLength >= 0x08000000) {
163 // range to big, limit
164 curLength = 0x08000000;
165 } else {
166 // cut off LSBs
167 curLength &= 0x07FFF000;
169 pushLength = ((curLength - 1) >> 12) | 0x8000;
171 m_rangeLengths.push_back(pushLength);
172 #ifdef __DEBUG__
173 if (m_storeDescriptions) {
174 // std::string has no ref counting, so swap it
175 // (it's used so we need half the space than wxString with wide chars)
176 if (descp) {
177 // we split the range so we have to duplicate it
178 m_rangeNames.push_back(*descp);
179 } else {
180 // push back empty string and swap
181 m_rangeNames.push_back(std::string());
182 descp = & * m_rangeNames.rbegin();
183 std::swap(*descp, it->Description);
186 #endif
187 realLength -= curLength;
188 startIP += curLength;
192 // Numbers are probably different:
193 // - ranges from map that are not blocked because of their level are not added to the table
194 // - some ranges from the map have to be split for the table
195 AddDebugLogLineN(logIPFilter, CFormat(wxT("Ranges in map: %d blocked ranges in table: %d")) % size % m_rangeIPs.size());
197 CIPFilterEvent evt(m_rangeIPs, m_rangeLengths, m_rangeNames);
198 wxPostEvent(m_owner, evt);
202 * This structure is used to contain the range-data in the rangemap.
204 struct rangeObject
206 bool operator==( const rangeObject& other ) const {
207 return AccessLevel == other.AccessLevel;
210 // Since descriptions are only used for debugging messages, there
211 // is no need to keep them in memory when running a non-debug build.
212 #ifdef __DEBUG__
213 //! Contains the user-description of the range.
214 std::string Description;
215 #endif
217 //! The AccessLevel for this filter.
218 uint8 AccessLevel;
221 //! The is the type of map used to store the IPs.
222 typedef CRangeMap<rangeObject, uint32> IPMap;
224 bool m_storeDescriptions;
226 // the generated filter
227 CIPFilter::RangeIPs m_rangeIPs;
228 CIPFilter::RangeLengths m_rangeLengths;
229 CIPFilter::RangeNames m_rangeNames;
231 wxEvtHandler* m_owner;
232 // temporary map for filter generation
233 IPMap m_result;
236 * Helper function.
238 * @param IPstart The start of the IP-range.
239 * @param IPend The end of the IP-range, must be less than or equal to IPstart.
240 * @param AccessLevel The AccessLevel of this range.
241 * @param Description The assosiated description of this range.
242 * @return true if the range was added, false if it was discarded.
244 * This function inserts the specified range into the IPMap. Invalid
245 * ranges where the AccessLevel is not within the range 0..255, or
246 * where IPEnd < IPstart not inserted.
248 bool AddIPRange(uint32 IPStart, uint32 IPEnd, uint16 AccessLevel, const char* DEBUG_ONLY(Description))
250 if (AccessLevel < 256) {
251 if (IPStart <= IPEnd) {
252 rangeObject item;
253 item.AccessLevel = AccessLevel;
254 #ifdef __DEBUG__
255 if (m_storeDescriptions) {
256 item.Description = Description;
258 #endif
260 m_result.insert(IPStart, IPEnd, item);
262 return true;
266 return false;
271 * Loads a IP-list from the specified file, can be text or zip.
273 * @return True if the file was loaded, false otherwise.
275 int LoadFromFile(const wxString& file)
277 const CPath path = CPath(file);
279 if (!path.FileExists() || TestDestroy()) {
280 return 0;
283 #ifdef __DEBUG__
284 m_storeDescriptions = theLogger.IsEnabled(logIPFilter);
285 #endif
287 const wxChar* ipfilter_files[] = {
288 wxT("ipfilter.dat"),
289 wxT("guardian.p2p"),
290 wxT("guarding.p2p"),
291 NULL
294 // Try to unpack the file, might be an archive
296 if (UnpackArchive(path, ipfilter_files).second != EFT_Text) {
297 AddLogLineM(true,
298 CFormat(_("Failed to load ipfilter.dat file '%s', unknown format encountered.")) % file);
299 return 0;
302 int filtercount = 0;
303 yyip_Bad = 0;
304 wxFFile readFile;
305 if (readFile.Open(path.GetRaw())) {
306 yyip_Line = 1;
307 yyiprestart(readFile.fp());
308 uint32 IPStart = 0;
309 uint32 IPEnd = 0;
310 uint32 IPAccessLevel = 0;
311 char * IPDescription;
312 uint32 time1 = GetTickCountFullRes();
313 while (yyiplex(IPStart, IPEnd, IPAccessLevel, IPDescription)) {
314 AddIPRange(IPStart, IPEnd, IPAccessLevel, IPDescription);
315 filtercount++;
317 uint32 time2 = GetTickCountFullRes();
318 AddDebugLogLineN(logIPFilter, CFormat(wxT("time for lexer: %.3f")) % ((time2-time1) / 1000.0));
319 } else {
320 AddLogLineM(true, CFormat(_("Failed to load ipfilter.dat file '%s', could not open file.")) % file);
321 return 0;
324 wxString msg = CFormat(wxPLURAL("Loaded %u IP-range from '%s'.", "Loaded %u IP-ranges from '%s'.", filtercount)) % filtercount % file;
325 if (yyip_Bad) {
326 msg << wxT(" ") << ( CFormat(wxPLURAL("%u malformed line was discarded.", "%u malformed lines were discarded.", yyip_Bad)) % yyip_Bad );
328 AddLogLineN(msg);
330 return filtercount;
335 ////////////////////////////////////////////////////////////
336 // CIPFilter
339 BEGIN_EVENT_TABLE(CIPFilter, wxEvtHandler)
340 EVT_MULE_IPFILTER_LOADED(CIPFilter::OnIPFilterEvent)
341 END_EVENT_TABLE()
346 * This function creates a text-file containing the specified text,
347 * but only if the file does not already exist.
349 static bool CreateDummyFile(const wxString& filename, const wxString& text)
351 // Create template files
352 if (!wxFileExists(filename)) {
353 CTextFile file;
355 if (file.Open(filename, CTextFile::write)) {
356 file.WriteLine(text);
357 return true;
360 return false;
364 CIPFilter::CIPFilter() :
365 m_ready(false),
366 m_startKADWhenReady(false),
367 m_connectToAnyServerWhenReady(false)
369 // Setup dummy files for the curious user.
370 const wxString normalDat = theApp->ConfigDir + wxT("ipfilter.dat");
371 const wxString normalMsg = wxString()
372 << wxT("# This file is used by aMule to store ipfilter lists downloaded\n")
373 << wxT("# through the auto-update functionality. Do not save ipfilter-\n")
374 << wxT("# ranges here that should not be overwritten by aMule.\n");
376 if (CreateDummyFile(normalDat, normalMsg)) {
377 // redownload if user deleted file
378 thePrefs::SetLastHTTPDownloadURL(HTTP_IPFilter, wxEmptyString);
381 const wxString staticDat = theApp->ConfigDir + wxT("ipfilter_static.dat");
382 const wxString staticMsg = wxString()
383 << wxT("# This file is used to store ipfilter-ranges that should\n")
384 << wxT("# not be overwritten by aMule. If you wish to keep a custom\n")
385 << wxT("# set of ipfilter-ranges that take precedence over ipfilter-\n")
386 << wxT("# ranges aquired through the auto-update functionality, then\n")
387 << wxT("# place them in this file. aMule will not change this file.");
389 CreateDummyFile(staticDat, staticMsg);
391 // First load currently available filter, so network connect is possible right after
392 // (in case filter download takes some time).
393 Reload();
394 // Check if update should be done only after that.
395 m_updateAfterLoading = thePrefs::IPFilterAutoLoad() && !thePrefs::IPFilterURL().IsEmpty();
399 void CIPFilter::Reload()
401 // We keep the current filter till the new one has been loaded.
402 CThreadScheduler::AddTask(new CIPFilterTask(this));
406 uint32 CIPFilter::BanCount() const
408 wxMutexLocker lock(m_mutex);
410 return m_rangeIPs.size();
414 bool CIPFilter::IsFiltered(uint32 IPTest, bool isServer)
416 if ((!thePrefs::IsFilteringClients() && !isServer) || (!thePrefs::IsFilteringServers() && isServer)) {
417 return false;
419 if (!m_ready) {
420 // Somebody connected before we even started the networks.
421 // Filter is not up yet, so block him.
422 AddDebugLogLineN(logIPFilter, CFormat(wxT("Filtered IP %s because filter isn't ready yet.")) % Uint32toStringIP(IPTest));
423 if (isServer) {
424 theStats::AddFilteredServer();
425 } else {
426 theStats::AddFilteredClient();
428 return true;
430 wxMutexLocker lock(m_mutex);
431 // The IP needs to be in host order
432 uint32 ip = wxUINT32_SWAP_ALWAYS(IPTest);
433 int imin = 0;
434 int imax = m_rangeIPs.size() - 1;
435 int i;
436 bool found = false;
437 while (imin <= imax) {
438 i = (imin + imax) / 2;
439 uint32 curIP = m_rangeIPs[i];
440 if (curIP <= ip) {
441 uint32 curLength = m_rangeLengths[i];
442 if (curLength >= 0x8000) {
443 curLength = ((curLength & 0x7fff) << 12) + 0xfff;
445 if (curIP + curLength >= ip) {
446 found = true;
447 break;
450 if (curIP > ip) {
451 imax = i - 1;
452 } else {
453 imin = i + 1;
456 if (found) {
457 AddDebugLogLineN(logIPFilter, CFormat(wxT("Filtered IP %s%s")) % Uint32toStringIP(IPTest)
458 % (i < (int)m_rangeNames.size() ? (wxT(" (") + wxString(char2unicode(m_rangeNames[i].c_str())) + wxT(")"))
459 : wxString(wxEmptyString)));
460 if (isServer) {
461 theStats::AddFilteredServer();
462 } else {
463 theStats::AddFilteredClient();
465 return true;
467 return false;
471 void CIPFilter::Update(const wxString& strURL)
473 if (!strURL.IsEmpty()) {
474 m_URL = strURL;
476 wxString filename = theApp->ConfigDir + wxT("ipfilter.download");
477 wxString oldfilename = theApp->ConfigDir + wxT("ipfilter.dat");
478 CHTTPDownloadThread *downloader = new CHTTPDownloadThread(m_URL, filename, oldfilename, HTTP_IPFilter);
480 downloader->Create();
481 downloader->Run();
486 void CIPFilter::DownloadFinished(uint32 result)
488 wxString datName = wxT("ipfilter.dat");
489 if (result == HTTP_Success) {
490 // download succeeded. proceed with ipfilter loading
491 wxString newDat = theApp->ConfigDir + wxT("ipfilter.download");
492 wxString oldDat = theApp->ConfigDir + datName;
494 if (wxFileExists(oldDat) && !wxRemoveFile(oldDat)) {
495 AddLogLineC(CFormat(_("Failed to remove %s file, aborting update.")) % datName);
496 result = HTTP_Error;
497 } else if (!wxRenameFile(newDat, oldDat)) {
498 AddLogLineC(CFormat(_("Failed to rename new %s file, aborting update.")) % datName);
499 result = HTTP_Error;
500 } else {
501 AddLogLineN(CFormat(_("Successfully updated %s")) % datName);
503 } else if (result == HTTP_Skipped) {
504 AddLogLineN(CFormat(_("Skipped download of %s, because requested file is not newer.")) % datName);
505 } else {
506 AddLogLineC(CFormat(_("Failed to download %s from %s")) % datName % m_URL);
509 if (result == HTTP_Success) {
510 // Reload both ipfilter files on success
511 Reload();
516 void CIPFilter::OnIPFilterEvent(CIPFilterEvent& evt)
519 wxMutexLocker lock(m_mutex);
520 std::swap(m_rangeIPs, evt.m_rangeIPs);
521 std::swap(m_rangeLengths, evt.m_rangeLengths);
522 std::swap(m_rangeNames, evt.m_rangeNames);
523 m_ready = true;
525 if (theApp->IsOnShutDown()) {
526 return;
528 AddLogLineN(_("IP filter is ready"));
530 if (thePrefs::IsFilteringClients()) {
531 theApp->clientlist->FilterQueues();
533 if (thePrefs::IsFilteringServers()) {
534 theApp->serverlist->FilterServers();
536 // Now start networks we didn't start earlier
537 if (m_connectToAnyServerWhenReady || m_startKADWhenReady) {
538 AddLogLineC(_("Connecting"));
540 if (m_connectToAnyServerWhenReady) {
541 m_connectToAnyServerWhenReady = false;
542 theApp->serverconnect->ConnectToAnyServer();
544 if (m_startKADWhenReady) {
545 m_startKADWhenReady = false;
546 theApp->StartKad();
548 theApp->ShowConnectionState(); // update connect button
549 if (thePrefs::GetSrcSeedsOn()) {
550 theApp->downloadqueue->LoadSourceSeeds();
552 // Trigger filter update if configured
553 if (m_updateAfterLoading) {
554 m_updateAfterLoading = false;
555 Update(thePrefs::IPFilterURL());
559 // File_checked_for_headers