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