Log is now on a new module so it can be included anywhere
[re6stnet.git] / vifibnet.py
1 #!/usr/bin/env python
2 import argparse, errno, os, select, sqlite3, subprocess, sys, time
3 import traceback
4 import upnpigd
5 import openvpn
6 import random
7 import propagation
8 import log
9
10 VIFIB_NET = "2001:db8:42::/48"
11 connection_dict = {} # to remember current connections we made
12 free_interface_set = set(('client1', 'client2', 'client3', 'client4', 'client5',
13 'client6', 'client7', 'client8', 'client9', 'client10'))
14
15 # TODO : How do we get our vifib ip ?
16 # TODO : flag in some way the peers that are connected to us so we don't connect to them
17 # Or maybe we just don't care,
18 class PeersDB:
19 def __init__(self, dbPath):
20 log.log('Connectiong to peers database', 4)
21 self.db = sqlite3.connect(dbPath, isolation_level=None)
22 log.log('Initializing peers database', 4)
23 self.db.execute("""CREATE TABLE IF NOT EXISTS peers
24 ( id INTEGER PRIMARY KEY AUTOINCREMENT,
25 ip TEXT NOT NULL,
26 port INTEGER NOT NULL,
27 proto TEXT NOT NULL,
28 used INTEGER NOT NULL)""")
29 self.db.execute("CREATE INDEX IF NOT EXISTS _peers_used ON peers(used)")
30 self.db.execute("UPDATE peers SET used = 0")
31
32 def getUnusedPeers(self, nPeers):
33 return self.db.execute("SELECT id, ip, port, proto FROM peers WHERE used = 0 "
34 "ORDER BY RANDOM() LIMIT ?", (nPeers,))
35
36 def usePeer(self, id):
37 log.log('Updating peers database : using peer ' + str(id), 5)
38 self.db.execute("UPDATE peers SET used = 1 WHERE id = ?", (id,))
39
40 def unusePeer(self, id):
41 log.log('Updating peers database : unusing peer ' + str(id), 5)
42 self.db.execute("UPDATE peers SET used = 0 WHERE id = ?", (id,))
43
44
45 def startBabel(**kw):
46 args = ['babeld',
47 '-C', 'redistribute local ip %s' % (config.ip),
48 '-C', 'redistribute local deny',
49 # Route VIFIB ip adresses
50 '-C', 'in ip %s' % VIFIB_NET,
51 # Route only addresse in the 'local' network,
52 # or other entire networks
53 #'-C', 'in ip %s' % (config.ip),
54 #'-C', 'in ip ::/0 le %s' % network_mask,
55 # Don't route other addresses
56 '-C', 'in deny',
57 '-d', str(config.verbose),
58 '-s',
59 ]
60 if config.babel_state:
61 args += '-S', config.babel_state
62 return subprocess.Popen(args + ['vifibnet'] + list(free_interface_set), **kw)
63
64 def getConfig():
65 global config
66 parser = argparse.ArgumentParser(
67 description='Resilient virtual private network application')
68 _ = parser.add_argument
69 _('--log-directory', default='/var/log',
70 help='Path to vifibnet logs directory')
71 _('--client-count', default=2, type=int,
72 help='Number of client connections')
73 # TODO : use maxpeer
74 _('--max-clients', default=10, type=int,
75 help='the number of peers that can connect to the server')
76 _('--refresh-time', default=60, type=int,
77 help='the time (seconds) to wait before changing the connections')
78 _('--refresh-count', default=1, type=int,
79 help='The number of connections to drop when refreshing the connections')
80 _('--db', default='/var/lib/vifibnet/peers.db',
81 help='Path to peers database')
82 _('--dh', required=True,
83 help='Path to dh file')
84 _('--babel-state', default='/var/lib/vifibnet/babel_state',
85 help='Path to babeld state-file')
86 _('--verbose', '-v', default=0, type=int,
87 help='Defines the verbose level')
88 # Temporary args - to be removed
89 _('--ip', required=True,
90 help='IPv6 of the server')
91 _('--entry-ip', default=None, help='entrypoint for the ring')
92 _('--entry-port', default=None, help='entrypoint for the ring')
93 # Openvpn options
94 _('openvpn_args', nargs=argparse.REMAINDER,
95 help="Common OpenVPN options (e.g. certificates)")
96 openvpn.config = config = parser.parse_args()
97 if config.openvpn_args[0] == "--":
98 del config.openvpn_args[0]
99
100 def startNewConnection(n):
101 try:
102 for id, ip, port, proto in peers_db.getUnusedPeers(n):
103 log.log('Establishing a connection with id %s (%s:%s)' % (id,ip,port), 2)
104 iface = free_interface_set.pop()
105 connection_dict[id] = ( openvpn.client( ip, '--dev', iface, '--proto', proto, '--rport', str(port),
106 stdout=os.open('%s/vifibnet.client.%s.log' % (config.log_directory, id), os.O_WRONLY|os.O_CREAT|os.O_TRUNC) ),
107 iface)
108 peers_db.usePeer(id)
109 except KeyError:
110 log.log("Can't establish connection with %s : no available interface" % ip, 2)
111 pass
112 except Exception:
113 traceback.print_exc()
114
115 def killConnection(id):
116 try:
117 log.log('Killing the connection with id ' + str(id), 2)
118 p, iface = connection_dict.pop(id)
119 p.kill()
120 free_interface_set.add(iface)
121 peers_db.unusePeer(id)
122 except KeyError:
123 log.log("Can't kill connection to " + peer + ": no existing connection", 1)
124 pass
125 except Exception:
126 log.log("Can't kill connection to " + peer + ": uncaught error", 1)
127 pass
128
129 def checkConnections():
130 for id in connection_dict.keys():
131 p, iface = connection_dict[id]
132 if p.poll() != None:
133 log.log('Connection with %s has failed with return code %s' % (id, p.returncode), 3)
134 free_interface_set.add(iface)
135 peers_db.unusePeer(id)
136 del connection_dict[id]
137
138 def refreshConnections():
139 checkConnections()
140 # Kill some random connections
141 try:
142 for i in range(0, max(0, len(connection_dict) - config.client_count + config.refresh_count)):
143 id = random.choice(connection_dict.keys())
144 killConnection(id)
145 except Exception:
146 pass
147 # Establish new connections
148 startNewConnection(config.client_count - len(connection_dict))
149
150 def handle_message(msg):
151 script_type, common_name = msg.split()
152 if script_type == 'client-connect':
153 log.log('Incomming connection from %s' % (common_name,), 3)
154 # TODO : check if we are not already connected to it
155 elif script_type == 'client-disconnect':
156 log.log('%s has disconnected' % (common_name,), 3)
157 else:
158 log.log('Unknow message recieved from the openvpn pipe : ' + msg, 1)
159
160 def main():
161 # Get arguments
162 getConfig()
163 log.verbose = config.verbose
164 (externalIp, externalPort) = upnpigd.GetExternalInfo(1194)
165
166 # Setup database
167 global peers_db # stop using global variables for everything ?
168 peers_db = PeersDB(config.db)
169
170 # Launch babel on all interfaces
171 log.log('Starting babel', 3)
172 babel = startBabel(stdout=os.open('%s/babeld.log' % (config.log_directory,), os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
173 stderr=subprocess.STDOUT)
174
175 # Create and open read_only pipe to get connect/disconnect events from openvpn
176 log.log('Creating pipe for openvpn events', 3)
177 r_pipe, write_pipe = os.pipe()
178 read_pipe = os.fdopen(r_pipe)
179
180 # Establish connections
181 log.log('Starting openvpn server', 3)
182 serverProcess = openvpn.server(config.ip, write_pipe, '--dev', 'vifibnet',
183 stdout=os.open('%s/vifibnet.server.log' % (config.log_directory,), os.O_WRONLY | os.O_CREAT | os.O_TRUNC))
184 startNewConnection(config.client_count)
185
186 # Timed refresh initializing
187 next_refresh = time.time() + config.refresh_time
188
189 # initializing the ring to propagate the peers
190 ring = propagation.Ring(None)
191
192 # main loop
193 try:
194 while True:
195 ready, tmp1, tmp2 = select.select([read_pipe], [], [],
196 max(0, next_refresh - time.time()))
197 if ready:
198 handle_message(read_pipe.readline())
199 if time.time() >= next_refresh:
200 refreshConnections()
201 next_refresh = time.time() + config.refresh_time
202 except KeyboardInterrupt:
203 return 0
204
205 if __name__ == "__main__":
206 main()
207
208 # TODO : remove incomming connections from avalaible peers
209