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 "UDPFirewallTester.h"
28 #include "../utils/UInt128.h"
29 #include "../utils/KadUDPKey.h"
30 #include "../routing/RoutingZone.h"
31 #include <common/Macros.h>
32 #include "Prefs.h"
33 #include "SearchManager.h"
34 #include "../../Logger.h"
35 #include "../../amule.h"
36 #include "../../ClientList.h"
37 #include "../../GetTickCount.h"
38 #include "../../NetworkFunctions.h"
39
40
41 using namespace Kademlia;
42
43 bool CUDPFirewallTester::m_firewalledUDP = false;
44 bool CUDPFirewallTester::m_firewalledLastStateUDP = false;
45 bool CUDPFirewallTester::m_isFWVerifiedUDP = false;
46 bool CUDPFirewallTester::m_nodeSearchStarted = false;
47 bool CUDPFirewallTester::m_timedOut = false;
48 uint8_t CUDPFirewallTester::m_fwChecksRunningUDP = 0;
49 uint8_t CUDPFirewallTester::m_fwChecksFinishedUDP = 0;
50 uint32_t CUDPFirewallTester::m_testStart = 0;
51 uint32_t CUDPFirewallTester::m_lastSucceededTime = 0;
52 CUDPFirewallTester::PossibleClientList CUDPFirewallTester::m_possibleTestClients;
53 CUDPFirewallTester::UsedClientList CUDPFirewallTester::m_usedTestClients;
54
55
IsFirewalledUDP(bool lastStateIfTesting)56 bool CUDPFirewallTester::IsFirewalledUDP(bool lastStateIfTesting)
57 {
58 if (CKademlia::IsRunningInLANMode()) {
59 return false;
60 }
61 if (!m_timedOut && IsFWCheckUDPRunning()) {
62 if (!m_firewalledUDP && CKademlia::IsFirewalled() && m_testStart != 0 && ::GetTickCount() - m_testStart > MIN2MS(6)
63 && !m_isFWVerifiedUDP /*For now we don't allow to get firewalled by timeouts if we have succeded a test before, might be changed later*/)
64 {
65 AddDebugLogLineN(logKadUdpFwTester, wxT("Timeout: Setting UDP status to firewalled after being unable to get results for 6 minutes"));
66 m_timedOut = true;
67 theApp->ShowConnectionState();
68 }
69 }
70 else if (m_timedOut && IsFWCheckUDPRunning()) {
71 return true; // firewallstate by timeout
72 }
73 else if (m_timedOut) {
74 wxFAIL;
75 }
76
77 if (lastStateIfTesting && IsFWCheckUDPRunning()) {
78 return m_firewalledLastStateUDP;
79 } else {
80 return m_firewalledUDP;
81 }
82 }
83
SetUDPFWCheckResult(bool succeeded,bool testCancelled,uint32_t fromIP,uint16_t incomingPort)84 void CUDPFirewallTester::SetUDPFWCheckResult(bool succeeded, bool testCancelled, uint32_t fromIP, uint16_t incomingPort)
85 {
86 // can be called on shutdown after KAD has been stopped
87 if (!CKademlia::IsRunning()) {
88 return;
89 }
90
91 // check if we actually requested a firewallcheck from this client
92 bool requested = false;
93 for (UsedClientList::iterator it = m_usedTestClients.begin(); it != m_usedTestClients.end(); ++it) {
94 if (it->contact.GetIPAddress() == fromIP) {
95 if (!IsFWCheckUDPRunning() && !m_firewalledUDP && m_isFWVerifiedUDP && m_lastSucceededTime + SEC2MS(10) > ::GetTickCount()
96 && incomingPort == CKademlia::GetPrefs()->GetInternKadPort() && CKademlia::GetPrefs()->GetUseExternKadPort()) {
97 // our test finished already in the last 10 seconds with being open because we received a proper result packet before
98 // however we now receive another answer packet on our incoming port (which is not unusal as both resultpackets are sent
99 // nearly at the same time and UDP doesn't cares if the order stays), while the one before was received on our extern port
100 // Because a proper forwarded intern port is more reliable to stay open than an extern port set by the NAT, we prefer
101 // intern ports and change the setting.
102 CKademlia::GetPrefs()->SetUseExternKadPort(false);
103 AddDebugLogLineN(logKadUdpFwTester, CFormat(wxT("Corrected UDP firewall result: Using open internal (%u) instead of open external port")) % incomingPort);
104 theApp->ShowConnectionState();
105 return;
106 } else if (it->answered) {
107 // we already received an answer. This may happen since all tests contain of two answer packets,
108 // but the answer could also be too late and we already counted it as failure.
109 return;
110 } else {
111 it->answered = true;
112 }
113 requested = true;
114 break;
115 }
116 }
117
118 if (!requested){
119 AddDebugLogLineN(logKadUdpFwTester, wxT("Unrequested UDPFWCheckResult from ") + KadIPToString(fromIP));
120 return;
121 }
122
123 if (!IsFWCheckUDPRunning()) {
124 // it's all over already
125 return;
126 }
127
128 if (m_fwChecksRunningUDP == 0) {
129 wxFAIL;
130 } else {
131 m_fwChecksRunningUDP--;
132 }
133
134 if (!testCancelled){
135 m_fwChecksFinishedUDP++;
136 if (succeeded) { //one positive result is enough
137 m_testStart = 0;
138 m_firewalledUDP = false;
139 m_isFWVerifiedUDP = true;
140 m_timedOut = false;
141 m_fwChecksFinishedUDP = UDP_FIREWALLTEST_CLIENTSTOASK; // don't do any more tests
142 m_fwChecksRunningUDP = 0; // all other tests are cancelled
143 m_possibleTestClients.clear(); // clear list, keep used clients list though
144 CSearchManager::CancelNodeFWCheckUDPSearch(); // cancel firewallnode searches if any are still active
145 // if this packet came to our internal port, explict set the interal port as used port from now on
146 if (incomingPort == CKademlia::GetPrefs()->GetInternKadPort()) {
147 CKademlia::GetPrefs()->SetUseExternKadPort(false);
148 AddDebugLogLineN(logKadUdpFwTester, wxT("New Kad Firewallstate (UDP): Open, using intern port"));
149 } else if (incomingPort == CKademlia::GetPrefs()->GetExternalKadPort() && incomingPort != 0) {
150 CKademlia::GetPrefs()->SetUseExternKadPort(true);
151 AddDebugLogLineN(logKadUdpFwTester, wxT("New Kad Firewallstate (UDP): Open, using extern port"));
152 }
153 theApp->ShowConnectionState();
154 return;
155 } else if (m_fwChecksFinishedUDP >= UDP_FIREWALLTEST_CLIENTSTOASK) {
156 // seems we are firewalled
157 m_testStart = 0;
158 AddDebugLogLineN(logKadUdpFwTester, wxT("New KAD Firewallstate (UDP): Firewalled"));
159 m_firewalledUDP = true;
160 m_isFWVerifiedUDP = true;
161 m_timedOut = false;
162 theApp->ShowConnectionState();
163 m_possibleTestClients.clear(); // clear list, keep used clients list though
164 CSearchManager::CancelNodeFWCheckUDPSearch(); // cancel firewallnode searches if any are still active
165 return;
166 } else
167 AddDebugLogLineN(logKadUdpFwTester, wxT("Kad UDP firewalltest from ") + KadIPToString(fromIP) + wxT(" result: Firewalled, continue testing"));
168 } else {
169 AddDebugLogLineN(logKadUdpFwTester, wxT("Kad UDP firewalltest from ") + KadIPToString(fromIP) + wxT(" cancelled"));
170 }
171 QueryNextClient();
172 }
173
ReCheckFirewallUDP(bool setUnverified)174 void CUDPFirewallTester::ReCheckFirewallUDP(bool setUnverified)
175 {
176 wxASSERT(m_fwChecksRunningUDP == 0);
177 m_fwChecksRunningUDP = 0;
178 m_fwChecksFinishedUDP = 0;
179 m_lastSucceededTime = 0;
180 m_testStart = ::GetTickCount();
181 m_timedOut = false;
182 m_firewalledLastStateUDP = m_firewalledUDP;
183 m_isFWVerifiedUDP = (m_isFWVerifiedUDP && !setUnverified);
184 CSearchManager::FindNodeFWCheckUDP(); // start a lookup for a random node to find suitable IPs
185 m_nodeSearchStarted = true;
186 CKademlia::GetPrefs()->FindExternKadPort(true);
187 }
188
Connected()189 void CUDPFirewallTester::Connected()
190 {
191 if (!m_nodeSearchStarted && IsFWCheckUDPRunning()) {
192 CSearchManager::FindNodeFWCheckUDP(); // start a lookup for a random node to find suitable IPs
193 m_nodeSearchStarted = true;
194 m_testStart = ::GetTickCount();
195 m_timedOut = false;
196 }
197 }
198
Reset()199 void CUDPFirewallTester::Reset()
200 {
201 m_firewalledUDP = false;
202 m_firewalledLastStateUDP = false;
203 m_isFWVerifiedUDP = false;
204 m_nodeSearchStarted = false;
205 m_timedOut = false;
206 m_fwChecksRunningUDP = 0;
207 m_fwChecksFinishedUDP = 0;
208 m_testStart = 0;
209 m_lastSucceededTime = 0;
210 CSearchManager::CancelNodeFWCheckUDPSearch(); // cancel firewallnode searches if any are still active
211 m_possibleTestClients.clear();
212 CKademlia::GetPrefs()->SetUseExternKadPort(true);
213 // keep the list of used clients
214 }
215
QueryNextClient()216 void CUDPFirewallTester::QueryNextClient()
217 { // try the next available client for the firewallcheck
218 if (!IsFWCheckUDPRunning() || !GetUDPCheckClientsNeeded() || CKademlia::GetPrefs()->FindExternKadPort(false)) {
219 return; // check if more tests are needed and wait till we know our extern port
220 }
221
222 if (!CKademlia::IsRunning() || CKademlia::GetRoutingZone() == NULL) {
223 wxFAIL;
224 return;
225 }
226
227 while (!m_possibleTestClients.empty()) {
228 CContact curContact = m_possibleTestClients.front();
229 m_possibleTestClients.pop_front();
230 // udp firewallchecks are not supported by clients with kadversion < 6
231 if (curContact.GetVersion() <= 5) {
232 continue;
233 }
234
235 // sanity - do not test ourself
236 if (wxUINT32_SWAP_ALWAYS(curContact.GetIPAddress()) == theApp->GetPublicIP() || curContact.GetClientID() == CKademlia::GetPrefs()->GetKadID()) {
237 continue;
238 }
239
240 // check if we actually requested a firewallcheck from this client at some point
241 bool alreadyRequested = false;
242 for (UsedClientList::const_iterator it = m_usedTestClients.begin(); it != m_usedTestClients.end(); ++it) {
243 if (it->contact.GetIPAddress() == curContact.GetIPAddress()) {
244 alreadyRequested = true;
245 break;
246 }
247 }
248
249 // check if we know its IP already from kademlia - we need an IP which was never used for UDP yet
250 if (!alreadyRequested && CKademlia::GetRoutingZone()->GetContact(curContact.GetIPAddress(), 0, false) == NULL) {
251 // ok, tell the clientlist to do the same search and start the check if ok
252 if (theApp->clientlist->DoRequestFirewallCheckUDP(curContact)) {
253 UsedClient_Struct sAdd = { curContact, false };
254 m_usedTestClients.push_front(sAdd);
255 m_fwChecksRunningUDP++;
256 break;
257 }
258 }
259 }
260 }
261