OpenVpn and babeld process should now always exit correctly when vifibnet exit
[re6stnet.git] / tunnel.py
1 import os, random, traceback, time, struct, subprocess, operator, math
2 import plib, utils, db
3
4 log = None
5 smooth = 0.3 # this is used to smooth the traffic sampling. Lower value
6 # mean more smooth
7
8
9 # Be carfull the refresh interval should let the routes be established
10
11
12 class Connection:
13
14 def __init__(self, address, write_pipe, hello, iface, prefix,
15 ovpn_args):
16 self.process = plib.client(address, write_pipe, hello, '--dev', iface,
17 *ovpn_args, stdout=os.open(os.path.join(log,
18 'vifibnet.client.%s.log' % (prefix,)),
19 os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
20 stderr=subprocess.STDOUT)
21
22 self.iface = iface
23 self.routes = 0
24 self._prefix = prefix
25 self._bandwidth = None
26 self._last_trafic = None
27
28 # TODO : update the stats
29 def refresh(self):
30 # Check that the connection is alive
31 if self.process.poll() != None:
32 utils.log('Connection with %s has failed with return code %s'
33 % (self._prefix, self.process.returncode), 3)
34 return False
35
36 self._updateBandwidth()
37 return True
38
39 def _updateBandwidth(self):
40 try:
41 f_rx = open('/sys/class/net/%s/statistics/rx_bytes' %
42 self.iface, 'r')
43 f_tx = open('/sys/class/net/%s/statistics/tx_bytes' %
44 self.iface, 'r')
45
46 trafic = int(f_rx.read()) + int(f_tx.read())
47 t = time.time()
48
49 if bool(self._last_trafic):
50 bw = (trafic - self._last_trafic) / (t -
51 self._last_trafic_update)
52 if bool(self._bandwidth):
53 self._bandwidth = ((1 - smooth) * self._bandwidth
54 + smooth * bw)
55 else:
56 self._bandwidth = bw
57
58 utils.log('New bandwidth calculated on iface %s : %s' %
59 (self.iface, self._bandwidth), 4)
60
61 self._last_trafic_update = t
62 self._last_trafic = trafic
63 except IOError: # This just means that the interface is downs
64 utils.log('Unable to calculate bandwidth on iface %s' %
65 self.iface, 4)
66
67
68 class TunnelManager:
69
70 def __init__(self, write_pipe, peer_db, openvpn_args, hello_interval,
71 refresh, connection_count, refresh_rate, iface_list, network):
72 self._write_pipe = write_pipe
73 self._peer_db = peer_db
74 self._connection_dict = {}
75 self._iface_to_prefix = {}
76 self._ovpn_args = openvpn_args
77 self._hello = hello_interval
78 self._refresh_time = refresh
79 self._network = network
80 self._net_len = len(network)
81 self._iface_list = iface_list
82 self.__indirect_connect = []
83 self.free_interface_set = set(('client1', 'client2', 'client3',
84 'client4', 'client5', 'client6',
85 'client7', 'client8', 'client9',
86 'client10', 'client11', 'client12'))
87 self.next_refresh = time.time()
88
89 self._client_count = int(math.ceil(float(connection_count) / 2.0))
90 self._refresh_count = int(math.ceil(refresh_rate * self._client_count))
91
92 def refresh(self):
93 utils.log('Refreshing the tunnels...', 2)
94 self._cleanDeads()
95 self._countRoutes()
96 self._removeSomeTunnels()
97 self._makeNewTunnels()
98 utils.log('Tunnels refreshed', 2)
99 self.next_refresh = time.time() + self._refresh_time
100
101 def _cleanDeads(self):
102 for prefix in self._connection_dict.keys():
103 if not self._connection_dict[prefix].refresh():
104 self._kill(prefix)
105 self._peer_db.flagPeer(prefix)
106
107 def _removeSomeTunnels(self):
108 # Get the candidates to killing
109 candidates = sorted(self._connection_dict, key=lambda p:
110 self._connection_dict[p].routes)
111 print max(0, len(self._connection_dict) - self._client_count + self._refresh_count) # DEBUG
112 print self._client_count
113 for prefix in candidates[0: max(0, len(self._connection_dict) -
114 self._client_count + self._refresh_count)]:
115 self._kill(prefix)
116
117 def _kill(self, prefix):
118 utils.log('Killing the connection with %s...' % (prefix,), 2)
119 connection = self._connection_dict.pop(prefix)
120 try:
121 connection.process.kill()
122 except OSError:
123 # If the process is already exited
124 pass
125 self.free_interface_set.add(connection.iface)
126 self._peer_db.unusePeer(prefix)
127 del self._iface_to_prefix[connection.iface]
128 utils.log('Connection with %s killed' % (prefix,), 2)
129
130 def _makeNewTunnels(self):
131 i = 0
132 utils.log('Trying to make %i new tunnels...' %
133 (self._client_count - len(self._connection_dict)), 5)
134 try:
135 for prefix, address in self._peer_db.getUnusedPeers(
136 self._client_count - len(self._connection_dict)):
137 utils.log('Establishing a connection with %s' % prefix, 2)
138 iface = self.free_interface_set.pop()
139 self._connection_dict[prefix] = Connection(address,
140 self._write_pipe, self._hello, iface,
141 prefix, self._ovpn_args)
142 self._iface_to_prefix[iface] = prefix
143 self._peer_db.usePeer(prefix)
144 i += 1
145 utils.log('%u new tunnels established' % (i,), 3)
146 except KeyError:
147 utils.log("""Can't establish connection with %s
148 : no available interface""" % prefix, 2)
149 except Exception:
150 traceback.print_exc()
151
152 def _countRoutes(self):
153 utils.log('Starting to count the routes on each interface...', 3)
154 self._indirect_connect = []
155 for iface in self._iface_to_prefix.keys():
156 self._connection_dict[self._iface_to_prefix[iface]].routes = 0
157 f = open('/proc/net/ipv6_route', 'r')
158 for line in f:
159 ip, subnet_size, iface = struct.unpack('32s x 2s 106x %ss x'
160 % (len(line) - 142), line)
161 ip = bin(int(ip, 16))[2:].rjust(128, '0')
162
163 if ip.startswith(self._network):
164 iface = iface.strip()
165 subnet_size = int(subnet_size, 16)
166 utils.log('Route on iface %s detected to %s/%s'
167 % (iface, ip, subnet_size), 8)
168 if iface in self._iface_to_prefix.keys():
169 self._connection_dict[self._iface_to_prefix[iface]].routes += 1
170 if iface in self._iface_list and self._net_len < subnet_size < 128:
171 prefix = ip[self._net_len:subnet_size]
172 utils.log('A route to %s has been discovered on the LAN'
173 % (prefix,), 3)
174 self._peer_db.blacklist(prefix)
175
176 utils.log("Routes have been counted", 3)
177 for p in self._connection_dict.keys():
178 utils.log('Routes on iface %s : %s' % (
179 self._connection_dict[p].iface,
180 self._connection_dict[p].routes), 5)
181
182 def killAll(self):
183 for prefix in self._connection_dict.keys():
184 self._kill(prefix)