1 # Written by Lucia D'Acunto
2 # see LICENSE.txt for license information
4 from time import strftime
5 from traceback import print_exc
12 from BaseLib.Core.BitTornado.BT1.MessageID import CRAWLER_NATCHECK, CRAWLER_NATTRAVERSAL
13 from BaseLib.Core.BitTornado.bencode import bencode, bdecode
14 from BaseLib.Core.NATFirewall.ConnectionCheck import ConnectionCheck
15 from BaseLib.Core.NATFirewall.NatTraversal import tryConnect, coordinateHolePunching
16 from BaseLib.Core.Overlay.SecureOverlay import OLPROTO_VER_EIGHTH, OLPROTO_VER_NINETH, SecureOverlay
17 from BaseLib.Core.Statistics.Crawler import Crawler
18 from BaseLib.Core.Utilities.utilities import show_permid, show_permid_short
19 from types import IntType, StringType, ListType, TupleType
20 from BaseLib.Core.simpledefs import *
26 class NatCheckMsgHandler:
31 if NatCheckMsgHandler.__single:
32 raise RuntimeError, "NatCheckMsgHandler is singleton"
33 NatCheckMsgHandler.__single = self
34 self.crawler_reply_callbacks = []
35 self._secure_overlay = SecureOverlay.getInstance()
37 self.crawler = Crawler.get_instance()
38 if self.crawler.am_crawler():
39 self._file = open("natcheckcrawler.txt", "a")
40 self._file.write("\n".join(("# " + "*" * 80, strftime("%Y/%m/%d %H:%M:%S"), "# Crawler started\n")))
42 self._file2 = open("nattraversalcrawler.txt", "a")
43 self._file2.write("\n".join(("# " + "*" * 80, strftime("%Y/%m/%d %H:%M:%S"), "# Crawler started\n")))
46 self.holePunchingIP = socket.gethostbyname(socket.gethostname())
53 def getInstance(*args, **kw):
54 if NatCheckMsgHandler.__single is None:
55 NatCheckMsgHandler(*args, **kw)
56 return NatCheckMsgHandler.__single
58 def register(self, launchmany):
60 print >> sys.stderr, "NatCheckMsgHandler: register"
62 self.session = launchmany.session
63 self.doNatCheckSender = None
64 self.registered = True
66 def doNatCheck(self, target_permid, selversion, request_callback):
68 The nat-check initiator_callback
71 # for Tribler versions < 4.5.0 : do nothing
72 # TODO: change OLPROTO_VER_EIGHTH to OLPROTO_VER_SEVENTH
73 if selversion < OLPROTO_VER_NINETH:
75 print >> sys.stderr, "NatCheckMsgHandler: Tribler version too old for NATCHECK: do nothing"
79 print >> sys.stderr, "NatCheckMsgHandler: do NATCHECK"
82 request_callback(CRAWLER_NATCHECK, "", callback=self.doNatCheckCallback)
86 def doNatCheckCallback(self, exc, permid):
91 print >> sys.stderr, "NATCHECK_REQUEST was sent to", show_permid_short(permid), exc
93 # Register peerinfo on file
94 self._file.write("; ".join((strftime("%Y/%m/%d %H:%M:%S"),
97 str(self._secure_overlay.get_dns_from_peerdb(permid)),
102 def gotDoNatCheckMessage(self, sender_permid, selversion, channel_id, payload, reply_callback):
104 The handle-request callback
107 self.doNatCheckSender = sender_permid
108 self.crawler_reply_callbacks.append(reply_callback)
112 print >>sys.stderr,"NatCheckMsgHandler: start_nat_type_detect()"
113 conn_check = ConnectionCheck.getInstance(self.session)
114 conn_check.try_start(self.natthreadcb_natCheckReplyCallback)
121 def natthreadcb_natCheckReplyCallback(self, ncr_data):
123 print >> sys.stderr, "NAT type: ", ncr_data
125 # send the message to the peer who has made the NATCHECK request, if any
126 if self.doNatCheckSender is not None:
128 ncr_msg = bencode(ncr_data)
131 if DEBUG: print >> sys.stderr, "error ncr_data:", ncr_data
134 print >> sys.stderr, "NatCheckMsgHandler:", ncr_data
136 # todo: make sure that natthreadcb_natCheckReplyCallback is always called for a request
137 # send replies to all the requests that have been received so far
138 for reply_callback in self.crawler_reply_callbacks:
139 reply_callback(ncr_msg, callback=self.natCheckReplySendCallback)
140 self.crawler_reply_callbacks = []
143 def natCheckReplySendCallback(self, exc, permid):
145 print >> sys.stderr, "NATCHECK_REPLY was sent to", show_permid_short(permid), exc
150 def gotNatCheckReplyMessage(self, permid, selversion, channel_id, channel_data, error, payload, request_callback):
152 The handle-reply callback
156 print >> sys.stderr, "NatCheckMsgHandler: gotNatCheckReplyMessage"
157 print >> sys.stderr, "NatCheckMsgHandler: error", error
159 # generic error: another crawler already obtained these results
160 self._file.write("; ".join((strftime("%Y/%m/%d %H:%M:%S"),
163 str(self._secure_overlay.get_dns_from_peerdb(permid)),
171 recv_data = bdecode(payload)
174 print >> sys.stderr, "bad encoded data:", payload
177 try: # check natCheckReply message
178 self.validNatCheckReplyMsg(recv_data)
179 except RuntimeError, e:
180 print >> sys.stderr, e
184 print >> sys.stderr, "NatCheckMsgHandler: received NAT_CHECK_REPLY message: ", recv_data
186 # Register peerinfo on file
187 self._file.write("; ".join((strftime("%Y/%m/%d %H:%M:%S"),
190 str(self._secure_overlay.get_dns_from_peerdb(permid)),
191 ":".join([str(x) for x in recv_data]),
195 # for Tribler versions < 5.0 : do nothing
196 if selversion < OLPROTO_VER_NINETH:
198 print >> sys.stderr, "NatCheckMsgHandler: Tribler version too old for NATTRAVERSAL: do nothing"
202 print >> sys.stderr, "NatCheckMsgHandler: do NATTRAVERSAL"
204 # Save peer in peerlist
205 if len(self.peerlist) == PEERLIST_LEN:
207 self.peerlist.append([permid,recv_data[1],recv_data[2]])
209 print >> sys.stderr, "NatCheckMsgHandler: peerlist length is: ", len(self.peerlist)
211 # Try to perform hole punching
212 if len(self.peerlist) >= 2:
213 self.tryHolePunching()
217 def validNatCheckReplyMsg(self, ncr_data):
219 if not type(ncr_data) == ListType:
220 raise RuntimeError, "NatCheckMsgHandler: received data is not valid. It must be a list of parameters."
223 if not type(ncr_data[0]) == StringType:
224 raise RuntimeError, "NatCheckMsgHandler: received data is not valid. The first element in the list must be a string."
227 if not type(ncr_data[1]) == IntType:
228 raise RuntimeError, "NatCheckMsgHandler: received data is not valid. The second element in the list must be an integer."
231 if not type(ncr_data[2]) == IntType:
232 raise RuntimeError, "NatCheckMsgHandler: received data is not valid. The third element in the list must be an integer."
235 if not type(ncr_data[3]) == StringType:
236 raise RuntimeError, "NatCheckMsgHandler: received data is not valid. The forth element in the list must be a string."
239 if not type(ncr_data[4]) == IntType:
240 raise RuntimeError, "NatCheckMsgHandler: received data is not valid. The fifth element in the list must be an integer."
243 if not type(ncr_data[5]) == StringType:
244 raise RuntimeError, "NatCheckMsgHandler: received data is not valid. The sixth element in the list must be a string."
247 if not type(ncr_data[6]) == IntType:
248 raise RuntimeError, "NatCheckMsgHandler: received data is not valid. The seventh element in the list must be an integer."
251 def tryHolePunching(self):
253 print >> sys.stderr, "NatCheckMsgHandler: first element in peerlist", self.peerlist[len(self.peerlist)-1]
254 print >> sys.stderr, "NatCheckMsgHandler: second element in peerlist", self.peerlist[len(self.peerlist)-2]
256 holePunchingPort = random.randrange(3200, 4200, 1)
257 holePunchingAddr = (self.holePunchingIP, holePunchingPort)
259 peer1 = self.peerlist[len(self.peerlist)-1]
260 peer2 = self.peerlist[len(self.peerlist)-2]
262 request_id = str(show_permid_short(peer1[0]) + show_permid_short(peer2[0]) + str(random.randrange(0, 1000, 1)))
264 self.udpConnect(peer1[0], request_id, holePunchingAddr)
265 self.udpConnect(peer2[0], request_id, holePunchingAddr)
267 # Register peerinfo on file
268 self._file2.write("; ".join((strftime("%Y/%m/%d %H:%M:%S"),
271 show_permid(peer1[0]),
274 str(self._secure_overlay.get_dns_from_peerdb(peer1[0])),
275 show_permid(peer2[0]),
278 str(self._secure_overlay.get_dns_from_peerdb(peer2[0])),
282 self.trav[request_id] = (None, None)
283 thread.start_new_thread(coordinateHolePunching, (peer1, peer2, holePunchingAddr))
285 def udpConnect(self, permid, request_id, holePunchingAddr):
288 print >> sys.stderr, "NatCheckMsgHandler: request UDP connection"
290 mh_data = request_id + ":" + holePunchingAddr[0] + ":" + str(holePunchingAddr[1])
293 print >> sys.stderr, "NatCheckMsgHandler: udpConnect message is", mh_data
296 mh_msg = bencode(mh_data)
299 if DEBUG: print >> sys.stderr, "NatCheckMsgHandler: error mh_data:", mh_data
303 self.crawler.send_request(permid, CRAWLER_NATTRAVERSAL, mh_msg, frequency=0, callback=self.udpConnectCallback)
306 print >> sys.stderr, "NatCheckMsgHandler: request for", show_permid_short(permid), "sent to crawler"
308 def udpConnectCallback(self, exc, permid):
312 print >> sys.stderr, "NATTRAVERSAL_REQUEST failed to", show_permid_short(permid), exc
314 # Register peerinfo on file
315 self._file2.write("; ".join((strftime("%Y/%m/%d %H:%M:%S"),
318 str(self._secure_overlay.get_dns_from_peerdb(permid)),
323 print >> sys.stderr, "NATTRAVERSAL_REQUEST was sent to", show_permid_short(permid), exc
326 def gotUdpConnectRequest(self, permid, selversion, channel_id, mh_msg, reply_callback):
329 print >> sys.stderr, "NatCheckMsgHandler: gotUdpConnectRequest from", show_permid_short(permid)
332 mh_data = bdecode(mh_msg)
335 print >> sys.stderr, "NatCheckMsgHandler: bad encoded data:", mh_msg
339 print >> sys.stderr, "NatCheckMsgHandler: gotUdpConnectRequest is", mh_data
343 request_id, host, port = mh_data.split(":")
346 print >> sys.stderr, "NatCheckMsgHandler: error in received data:", mh_data
349 coordinator = (host, int(port))
352 print >> sys.stderr, "NatCheckMsgHandler: coordinator address is", coordinator
354 mhr_data = request_id + ":" + tryConnect(coordinator)
356 # Report back to coordinator
358 mhr_msg = bencode(mhr_data)
361 print >> sys.stderr, "NatCheckMsgHandler: error in encoding data:", mhr_data
364 reply_callback(mhr_msg, callback=self.udpConnectReplySendCallback)
366 def udpConnectReplySendCallback(self, exc, permid):
369 print >> sys.stderr, "NATTRAVERSAL_REPLY was sent to", show_permid_short(permid), exc
375 def gotUdpConnectReply(self, permid, selversion, channel_id, channel_data, error, mhr_msg, request_callback):
378 print >> sys.stderr, "NatCheckMsgHandler: gotMakeHoleReplyMessage"
381 mhr_data = bdecode(mhr_msg)
384 print >> sys.stderr, "NatCheckMsgHandler: bad encoded data:", mhr_msg
388 print >> sys.stderr, "NatCheckMsgHandler: message is", mhr_data
391 request_id, reply = mhr_data.split(":")
394 print >> sys.stderr, "NatCheckMsgHandler: error in received data:", mhr_data
398 print >> sys.stderr, "NatCheckMsgHandler: request_id is", request_id
400 if request_id in self.trav:
402 print >> sys.stderr, "NatCheckMsgHandler: request_id is in the list"
403 peer, value = self.trav[request_id]
404 if peer == None: # first peer reply
406 print >> sys.stderr, "NatCheckMsgHandler: first peer reply"
407 self.trav[request_id] = ( (permid, self._secure_overlay.get_dns_from_peerdb(permid)), reply )
408 elif type(peer) == TupleType: # second peer reply
410 print >> sys.stderr, "NatCheckMsgHandler: second peer reply"
412 # Register peerinfo on file
413 self._file2.write("; ".join((strftime("%Y/%m/%d %H:%M:%S"),
416 show_permid(peer[0]),
420 str(self._secure_overlay.get_dns_from_peerdb(permid)),
424 del self.trav[request_id]