instrumentation: add next-share/
[cs-p2p-next.git] / instrumentation / next-share / BaseLib / Core / NATFirewall / ConnectionCheck.py
1 import sys
2 from time import sleep
3 import thread
4 import random
5 from BaseLib.Core.NATFirewall.NatCheck import GetNATType
6 from BaseLib.Core.NATFirewall.TimeoutCheck import GetTimeout
7
8 DEBUG = False
9
10 class ConnectionCheck:
11
12     __single = None
13
14     def __init__(self, session):
15         if ConnectionCheck.__single:
16             raise RuntimeError, "ConnectionCheck is singleton"
17         ConnectionCheck.__single = self
18         self._lock = thread.allocate_lock()
19         self._running = False
20         self.session = session
21         self.permid = self.session.get_permid()
22         self.nat_type = None
23         self.nat_timeout = 0
24         self._nat_callbacks = [] # list with callback functions that want to know the nat_type
25         self.natcheck_reply_callbacks = [] # list with callback functions that want to send a natcheck_reply message
26
27     @staticmethod
28     def getInstance(*args, **kw):
29         if ConnectionCheck.__single is None:
30             ConnectionCheck(*args, **kw)
31         return ConnectionCheck.__single
32
33     def try_start(self, reply_callback = None):
34
35         if reply_callback: self.natcheck_reply_callbacks.append(reply_callback)
36
37         if DEBUG:
38             if self._running:
39                 print >>sys.stderr, "natcheckmsghandler: the thread is already running"
40             else:
41                 print >>sys.stderr, "natcheckmsghandler: starting the thread"
42             
43         if not self._running:
44             thread.start_new_thread(self.run, ())
45
46             while True:
47                 sleep(0)
48                 if self._running:
49                     break
50
51     def run(self):
52         self._lock.acquire()
53         self._running = True
54         self._lock.release()
55
56         try:
57             self.nat_discovery()
58
59         finally:
60             self._lock.acquire()
61             self._running = False
62             self._lock.release()
63
64     def timeout_check(self, pingback):
65         """
66         Find out NAT timeout
67         """
68         return GetTimeout(pingback)
69
70     def natcheck(self, in_port, server1, server2):
71         """
72         Find out NAT type and public address and port
73         """        
74         nat_type, ex_ip, ex_port, in_ip = GetNATType(in_port, server1, server2)
75         if DEBUG: print >> sys.stderr, "NATCheck:", "NAT Type: " + nat_type[1]
76         if DEBUG: print >> sys.stderr, "NATCheck:", "Public Address: " + ex_ip + ":" + str(ex_port)
77         if DEBUG: print >> sys.stderr, "NATCheck:", "Private Address: " + in_ip + ":" + str(in_port)
78         return nat_type, ex_ip, ex_port, in_ip
79
80     def get_nat_type(self, callback=None):
81         """
82         When a callback parameter is supplied it will always be
83         called. When the NAT-type is already known the callback will
84         be made instantly. Otherwise, the callback will be made when
85         the NAT discovery has finished.
86         """
87         if self.nat_type:
88             if callback:
89                 callback(self.nat_type)
90             return self.nat_type
91         else:
92             if callback:
93                 self._nat_callbacks.append(callback)
94             self.try_start()
95             return "Unknown NAT/Firewall"
96
97     def _perform_nat_type_notification(self):
98         nat_type = self.get_nat_type()
99         callbacks = self._nat_callbacks
100         self._nat_callbacks = []
101
102         for callback in callbacks:
103             try:
104                 callback(nat_type)
105             except:
106                 pass
107
108     def nat_discovery(self):
109         """
110         Main method of the class: launches nat discovery algorithm
111         """
112         in_port = self.session.get_puncturing_internal_port()
113         stun_servers = self.session.get_stun_servers()
114         random.seed()
115         random.shuffle(stun_servers)
116         stun1 = stun_servers[1]
117         stun2 = stun_servers[0]
118         pingback_servers = self.session.get_pingback_servers()
119         random.shuffle(pingback_servers)
120
121         if DEBUG: print >> sys.stderr, "NATCheck:", 'Starting ConnectionCheck on %s %s %s' % (in_port, stun1, stun2)
122
123         performed_nat_type_notification = False
124
125         # Check what kind of NAT the peer is behind
126         nat_type, ex_ip, ex_port, in_ip = self.natcheck(in_port, stun1, stun2)
127         self.nat_type = nat_type[1]
128
129         # notify any callbacks interested in the nat_type only
130         self._perform_nat_type_notification()
131         performed_nat_type_notification = True
132
133
134         # If there is any callback interested, check the UDP timeout of the NAT the peer is behind
135         if len(self.natcheck_reply_callbacks):
136
137             if nat_type[0] > 0:
138                 for pingback in pingback_servers:
139                     if DEBUG: print >> sys.stderr, "NatCheck: pingback is:", pingback
140                     self.nat_timeout = self.timeout_check(pingback)
141                     if self.nat_timeout <= 0: break
142                 if DEBUG: print >> sys.stderr, "NATCheck: Nat UDP timeout is: ", str(self.nat_timeout)
143
144             self.nat_params = [nat_type[1], nat_type[0], self.nat_timeout, ex_ip, int(ex_port), in_ip, in_port]
145             if DEBUG: print >> sys.stderr, "NATCheck:", str(self.nat_params)
146
147             # notify any callbacks interested in sending a natcheck_reply message
148             for reply_callback in self.natcheck_reply_callbacks:
149                 reply_callback(self.nat_params)
150             self.natcheck_reply_callbacks = []
151
152         if not performed_nat_type_notification:
153             self._perform_nat_type_notification()