Cleaner server connection
[re6stnet.git] / vifibnet.py
1 #!/usr/bin/env python
2 import argparse, errno, math, os, select, subprocess, sys, time, traceback
3 from argparse import ArgumentParser
4 from OpenSSL import crypto
5 import db, plib, upnpigd, utils, tunnel
6
7 class ArgParser(ArgumentParser):
8
9 def convert_arg_line_to_args(self, arg_line):
10 arg_line = arg_line.split('#')[0].rstrip()
11 if arg_line:
12 for arg in ('--' + arg_line.lstrip('--')).split():
13 if arg.strip():
14 yield arg
15
16 def ovpnArgs(optional_args, ca_path, cert_path):
17 # Treat openvpn arguments
18 if optional_args[0] == "--":
19 del optional_args[0]
20 optional_args.append('--ca')
21 optional_args.append(ca_path)
22 optional_args.append('--cert')
23 optional_args.append(cert_path)
24 return optional_args
25
26 def getConfig():
27 parser = ArgParser(fromfile_prefix_chars='@',
28 description='Resilient virtual private network application')
29 _ = parser.add_argument
30 # Server address SHOULD be a vifib address ( else requests will be denied )
31 _('--server', required=True,
32 help="VPN address of the discovery peer server")
33 _('--server-port', required=True, type=int,
34 help="VPN port of the discovery peer server")
35 _('-log', '-l', default='/var/log',
36 help='Path to vifibnet logs directory')
37 _('--tunnel-refresh', default=300, type=int,
38 help='the time (seconds) to wait before changing the connections')
39 _('--peers-db-refresh', default=3600, type=int,
40 help='the time (seconds) to wait before refreshing the peers db')
41 _('--db', default='/var/lib/vifibnet/peers.db',
42 help='Path to peers database')
43 _('--dh', required=True,
44 help='Path to dh file')
45 _('--babel-state', default='/var/lib/vifibnet/babel_state',
46 help='Path to babeld state-file')
47 _('--hello', type=int, default=30,
48 help='Hello interval for babel, in seconds')
49 _('-w', '--wireless', action='store_true',
50 help='Set all interfaces to be treated as wireless interfaces ( in babel )')
51 _('--verbose', '-v', default=0, type=int,
52 help='Defines the verbose level')
53 _('--ca', required=True,
54 help='Path to the certificate authority file')
55 _('--cert', required=True,
56 help='Path to the certificate file')
57 ipconfig = parser.add_mutually_exclusive_group()
58 __ = ipconfig.add_argument
59 __('--ip', default=None, dest='address', action='append', nargs=3,
60 help='Ip address, port and protocol advertised to other vpn nodes')
61 __('--internal-port', default=1194,
62 help='Internal port to listen on for incomming connections')
63 # args to be removed ?
64 _('--proto', default='udp',
65 help='The protocol used by other peers to connect')
66 _('--connection-count', default=20, type=int,
67 help='Number of tunnels')
68 _('--refresh-rate', default=0.05, type=float,
69 help='The ratio of connections to drop when refreshing the connections')
70 # Openvpn options
71 _('openvpn_args', nargs=argparse.REMAINDER,
72 help="Common OpenVPN options (e.g. certificates)")
73 return parser.parse_args()
74
75 def main():
76 # Get arguments
77 config = getConfig()
78 manual = bool(config.address)
79 network = utils.networkFromCa(config.ca)
80 internal_ip, prefix = utils.ipFromCert(network, config.cert)
81 openvpn_args = ovpnArgs(config.openvpn_args, config.ca, config.cert)
82
83 # Set global variables
84 tunnel.log = config.log
85 utils.verbose = plib.verbose = config.verbose
86
87 # Create and open read_only pipe to get server events
88 utils.log('Creating pipe for server events', 3)
89 r_pipe, write_pipe = os.pipe()
90 read_pipe = os.fdopen(r_pipe)
91
92 # Init db and tunnels
93 if manual:
94 utils.log('Manual external configuration', 3)
95 else:
96 utils.log('Attempting automatic configuration via UPnP', 4)
97 try:
98 external_ip, external_port = upnpigd.ForwardViaUPnP(config.internal_port)
99 config.address = [[external_ip, external_port, 'udp'],
100 [external_ip, external_port, 'tcp-client']]
101 except Exception:
102 utils.log('An atempt to forward a port via UPnP failed', 4)
103
104 peer_db = db.PeerManager(config.db, config.server, config.server_port,
105 config.peers_db_refresh, config.address, internal_ip, prefix, manual, 200)
106 tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args, config.hello,
107 config.tunnel_refresh, config.connection_count, config.refresh_rate)
108
109 # Launch babel on all interfaces. WARNING : you have to be root to start babeld
110 interface_list = ['vifibnet'] + list(tunnel_manager.free_interface_set)
111 router = plib.router(network, internal_ip, interface_list, config.wireless, config.hello,
112 stdout=os.open(os.path.join(config.log, 'vifibnet.babeld.log'),
113 os.O_WRONLY | os.O_CREAT | os.O_TRUNC), stderr=subprocess.STDOUT)
114
115 # Establish connections
116 server_process = plib.server(internal_ip, network, config.connection_count, config.dh, write_pipe,
117 config.internal_port, config.proto, config.hello, '--dev', 'vifibnet', *openvpn_args,
118 stdout=os.open(os.path.join(config.log, 'vifibnet.server.log'), os.O_WRONLY | os.O_CREAT | os.O_TRUNC))
119 tunnel_manager.refresh()
120
121 # main loop
122 try:
123 while True:
124 ready, tmp1, tmp2 = select.select([read_pipe], [], [],
125 max(0, min(tunnel_manager.next_refresh, peer_db.next_refresh) - time.time()))
126 if ready:
127 peer_db.handle_message(read_pipe.readline())
128 if time.time() >= peer_db.next_refresh:
129 peer_db.refresh()
130 if time.time() >= tunnel_manager.next_refresh:
131 tunnel_manager.refresh()
132 except KeyboardInterrupt:
133 return 0
134
135 if __name__ == "__main__":
136 main()
137