instrumentation: add next-share/
[cs-p2p-next.git] / instrumentation / next-share / BaseLib / Core / NATFirewall / NatCheckMsgHandler.py
1 # Written by Lucia D'Acunto
2 # see LICENSE.txt for license information
3
4 from time import strftime
5 from traceback import print_exc
6 import datetime
7 import random
8 import socket
9 import sys
10 import thread
11
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 *
21
22 DEBUG = False
23
24 PEERLIST_LEN = 100
25
26 class NatCheckMsgHandler:
27
28     __single = None
29
30     def __init__(self):
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()
36
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")))
41             self._file.flush()
42             self._file2 = open("nattraversalcrawler.txt", "a")
43             self._file2.write("\n".join(("# " + "*" * 80, strftime("%Y/%m/%d %H:%M:%S"), "# Crawler started\n")))
44             self._file2.flush()
45             self.peerlist = []
46             self.holePunchingIP = socket.gethostbyname(socket.gethostname())
47             self.trav = {}
48
49         else:
50             self._file = None
51
52     @staticmethod
53     def getInstance(*args, **kw):
54         if NatCheckMsgHandler.__single is None:
55             NatCheckMsgHandler(*args, **kw)
56         return NatCheckMsgHandler.__single
57
58     def register(self, launchmany):
59         if DEBUG:
60             print >> sys.stderr, "NatCheckMsgHandler: register"
61
62         self.session = launchmany.session
63         self.doNatCheckSender = None
64         self.registered = True
65
66     def doNatCheck(self, target_permid, selversion, request_callback):
67         """
68         The nat-check initiator_callback
69         """
70
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:
74             if DEBUG:
75                 print >> sys.stderr, "NatCheckMsgHandler: Tribler version too old for NATCHECK: do nothing"
76             return False
77             
78         if DEBUG:
79             print >> sys.stderr, "NatCheckMsgHandler: do NATCHECK"
80             
81         # send the message
82         request_callback(CRAWLER_NATCHECK, "", callback=self.doNatCheckCallback)
83
84         return True
85
86     def doNatCheckCallback(self, exc, permid):
87
88         if exc is not None:
89             return False
90             if DEBUG:
91                 print >> sys.stderr, "NATCHECK_REQUEST was sent to", show_permid_short(permid), exc
92
93         # Register peerinfo on file
94         self._file.write("; ".join((strftime("%Y/%m/%d %H:%M:%S"),
95                                     "REQUEST",
96                                     show_permid(permid),
97                                     str(self._secure_overlay.get_dns_from_peerdb(permid)),
98                                     "\n")))
99         self._file.flush()
100         return True
101
102     def gotDoNatCheckMessage(self, sender_permid, selversion, channel_id, payload, reply_callback):
103         """
104         The handle-request callback
105         """
106
107         self.doNatCheckSender = sender_permid
108         self.crawler_reply_callbacks.append(reply_callback)
109
110         try:
111             if DEBUG:
112                 print >>sys.stderr,"NatCheckMsgHandler: start_nat_type_detect()"
113             conn_check = ConnectionCheck.getInstance(self.session)
114             conn_check.try_start(self.natthreadcb_natCheckReplyCallback)
115         except:
116             print_exc()
117             return False
118
119         return True
120         
121     def natthreadcb_natCheckReplyCallback(self, ncr_data):
122         if DEBUG:
123             print >> sys.stderr, "NAT type: ", ncr_data
124
125         # send the message to the peer who has made the NATCHECK request, if any
126         if self.doNatCheckSender is not None:
127             try:
128                 ncr_msg = bencode(ncr_data)
129             except:
130                 print_exc()
131                 if DEBUG: print >> sys.stderr, "error ncr_data:", ncr_data
132                 return False
133             if DEBUG:
134                 print >> sys.stderr, "NatCheckMsgHandler:", ncr_data
135
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 = []
141             
142
143     def natCheckReplySendCallback(self, exc, permid):
144         if DEBUG:
145             print >> sys.stderr, "NATCHECK_REPLY was sent to", show_permid_short(permid), exc
146         if exc is not None:
147             return False
148         return True
149
150     def gotNatCheckReplyMessage(self, permid, selversion, channel_id, channel_data, error, payload, request_callback):
151         """
152         The handle-reply callback
153         """
154         if error:
155             if DEBUG:
156                 print >> sys.stderr, "NatCheckMsgHandler: gotNatCheckReplyMessage"
157                 print >> sys.stderr, "NatCheckMsgHandler: error", error
158
159             # generic error: another crawler already obtained these results
160             self._file.write("; ".join((strftime("%Y/%m/%d %H:%M:%S"),
161                                         "  REPLY",
162                                         show_permid(permid),
163                                         str(self._secure_overlay.get_dns_from_peerdb(permid)),
164                                         "ERROR(%d)" % error,
165                                         payload,
166                                         "\n")))
167             self._file.flush()
168
169         else:
170             try:
171                 recv_data = bdecode(payload)
172             except:
173                 print_exc()
174                 print >> sys.stderr, "bad encoded data:", payload
175                 return False
176
177             try:    # check natCheckReply message
178                 self.validNatCheckReplyMsg(recv_data)
179             except RuntimeError, e:
180                 print >> sys.stderr, e
181                 return False
182
183             if DEBUG:
184                 print >> sys.stderr, "NatCheckMsgHandler: received NAT_CHECK_REPLY message: ", recv_data
185
186             # Register peerinfo on file
187             self._file.write("; ".join((strftime("%Y/%m/%d %H:%M:%S"),
188                                         "  REPLY",
189                                         show_permid(permid),
190                                         str(self._secure_overlay.get_dns_from_peerdb(permid)),
191                                         ":".join([str(x) for x in recv_data]),
192                                         "\n")))
193             self._file.flush()
194
195             # for Tribler versions < 5.0 : do nothing
196             if selversion < OLPROTO_VER_NINETH:
197                 if DEBUG:
198                     print >> sys.stderr, "NatCheckMsgHandler: Tribler version too old for NATTRAVERSAL: do nothing"
199                 return True
200                 
201             if DEBUG:
202                 print >> sys.stderr, "NatCheckMsgHandler: do NATTRAVERSAL"
203
204             # Save peer in peerlist
205             if len(self.peerlist) == PEERLIST_LEN:
206                 del self.peerlist[0]
207             self.peerlist.append([permid,recv_data[1],recv_data[2]])
208             if DEBUG:
209                 print >> sys.stderr, "NatCheckMsgHandler: peerlist length is: ", len(self.peerlist)
210
211             # Try to perform hole punching
212             if len(self.peerlist) >= 2:
213                 self.tryHolePunching()
214
215         return True
216
217     def validNatCheckReplyMsg(self, ncr_data):
218
219         if not type(ncr_data) == ListType:
220             raise RuntimeError, "NatCheckMsgHandler: received data is not valid. It must be a list of parameters."
221             return False
222             
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."
225             return False
226             
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."
229             return False
230             
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."
233             return False
234             
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."
237             return False
238             
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."
241             return False
242             
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."
245             return False
246             
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."
249             return False
250
251     def tryHolePunching(self):
252         if DEBUG:
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]
255
256         holePunchingPort = random.randrange(3200, 4200, 1)
257         holePunchingAddr = (self.holePunchingIP, holePunchingPort)
258         
259         peer1 = self.peerlist[len(self.peerlist)-1]
260         peer2 = self.peerlist[len(self.peerlist)-2]
261
262         request_id = str(show_permid_short(peer1[0]) + show_permid_short(peer2[0]) + str(random.randrange(0, 1000, 1)))
263
264         self.udpConnect(peer1[0], request_id, holePunchingAddr)
265         self.udpConnect(peer2[0], request_id, holePunchingAddr)
266
267         # Register peerinfo on file
268         self._file2.write("; ".join((strftime("%Y/%m/%d %H:%M:%S"),
269                                     "REQUEST",
270                                     request_id,
271                                     show_permid(peer1[0]),
272                                     str(peer1[1]),
273                                     str(peer1[2]),
274                                     str(self._secure_overlay.get_dns_from_peerdb(peer1[0])),
275                                     show_permid(peer2[0]),
276                                     str(peer2[1]),
277                                     str(peer2[2]),
278                                     str(self._secure_overlay.get_dns_from_peerdb(peer2[0])),
279                                     "\n")))
280         self._file2.flush()
281
282         self.trav[request_id] = (None, None)
283         thread.start_new_thread(coordinateHolePunching, (peer1, peer2, holePunchingAddr))
284
285     def udpConnect(self, permid, request_id, holePunchingAddr):
286
287         if DEBUG:
288             print >> sys.stderr, "NatCheckMsgHandler: request UDP connection"
289
290         mh_data = request_id + ":" + holePunchingAddr[0] + ":" + str(holePunchingAddr[1])
291
292         if DEBUG:
293             print >> sys.stderr, "NatCheckMsgHandler: udpConnect message is", mh_data
294
295         try:
296             mh_msg = bencode(mh_data)
297         except:
298             print_exc()
299             if DEBUG: print >> sys.stderr, "NatCheckMsgHandler: error mh_data:", mh_data
300             return False
301
302         # send the message
303         self.crawler.send_request(permid, CRAWLER_NATTRAVERSAL, mh_msg, frequency=0, callback=self.udpConnectCallback)
304
305         if DEBUG:
306             print >> sys.stderr, "NatCheckMsgHandler: request for", show_permid_short(permid), "sent to crawler"
307
308     def udpConnectCallback(self, exc, permid):
309
310         if exc is not None:
311             if DEBUG:
312                 print >> sys.stderr, "NATTRAVERSAL_REQUEST failed to", show_permid_short(permid), exc
313
314             # Register peerinfo on file
315             self._file2.write("; ".join((strftime("%Y/%m/%d %H:%M:%S"),
316                                     "REQUEST FAILED",
317                                     show_permid(permid),
318                                     str(self._secure_overlay.get_dns_from_peerdb(permid)),
319                                     "\n")))
320             return False
321
322         if DEBUG:
323             print >> sys.stderr, "NATTRAVERSAL_REQUEST was sent to", show_permid_short(permid), exc
324         return True
325         
326     def gotUdpConnectRequest(self, permid, selversion, channel_id, mh_msg, reply_callback):
327
328         if DEBUG:
329             print >> sys.stderr, "NatCheckMsgHandler: gotUdpConnectRequest from", show_permid_short(permid)
330
331         try:
332             mh_data = bdecode(mh_msg)
333         except:
334             print_exc()
335             print >> sys.stderr, "NatCheckMsgHandler: bad encoded data:", mh_msg
336             return False
337
338         if DEBUG:
339             print >> sys.stderr, "NatCheckMsgHandler: gotUdpConnectRequest is", mh_data
340
341         
342         try:
343             request_id, host, port = mh_data.split(":")
344         except:
345             print_exc()
346             print >> sys.stderr, "NatCheckMsgHandler: error in received data:", mh_data
347             return False
348
349         coordinator = (host, int(port))
350
351         if DEBUG:
352             print >> sys.stderr, "NatCheckMsgHandler: coordinator address is", coordinator
353
354         mhr_data = request_id + ":" + tryConnect(coordinator)
355
356         # Report back to coordinator
357         try:
358             mhr_msg = bencode(mhr_data)
359         except:
360             print_exc()
361             print >> sys.stderr, "NatCheckMsgHandler: error in encoding data:", mhr_data
362             return False
363
364         reply_callback(mhr_msg, callback=self.udpConnectReplySendCallback)
365
366     def udpConnectReplySendCallback(self, exc, permid):
367
368         if DEBUG:
369             print >> sys.stderr, "NATTRAVERSAL_REPLY was sent to", show_permid_short(permid), exc
370         if exc is not None:
371             return False
372         return True
373
374         
375     def gotUdpConnectReply(self, permid, selversion, channel_id, channel_data, error, mhr_msg, request_callback):
376
377         if DEBUG:
378             print >> sys.stderr, "NatCheckMsgHandler: gotMakeHoleReplyMessage"
379
380         try:
381             mhr_data = bdecode(mhr_msg)
382         except:
383             print_exc()
384             print >> sys.stderr, "NatCheckMsgHandler: bad encoded data:", mhr_msg
385             return False
386
387         if DEBUG:
388             print >> sys.stderr, "NatCheckMsgHandler: message is", mhr_data
389
390         try:
391             request_id, reply = mhr_data.split(":")
392         except:
393             print_exc()
394             print >> sys.stderr, "NatCheckMsgHandler: error in received data:", mhr_data
395             return False
396
397         if DEBUG:
398             print >> sys.stderr, "NatCheckMsgHandler: request_id is", request_id
399
400         if request_id in self.trav:
401             if DEBUG:
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
405                 if DEBUG:
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
409                 if DEBUG:
410                     print >> sys.stderr, "NatCheckMsgHandler: second peer reply"
411                     
412                 # Register peerinfo on file
413                 self._file2.write("; ".join((strftime("%Y/%m/%d %H:%M:%S"),
414                                                     "  REPLY",
415                                                     request_id,
416                                                     show_permid(peer[0]),
417                                                     str(peer[1]),
418                                                     value,
419                                                     show_permid(permid),
420                                                     str(self._secure_overlay.get_dns_from_peerdb(permid)),
421                                                     reply,
422                                                     "\n")))
423
424                 del self.trav[request_id]
425
426         self._file2.flush()
427