Fixed prefix=0 bug
[re6stnet.git] / vifibnet.py
1 #!/usr/bin/env python
2 import argparse, errno, os, select, subprocess, sqlite3, time, logging
3 from argparse import ArgumentParser
4 import db, plib, upnpigd, utils, tunnel
5
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 if arg_line.startswith('@'):
13 yield arg_line
14 return
15 for arg in ('--' + arg_line.lstrip('--')).split():
16 if arg.strip():
17 yield arg
18
19
20 def ovpnArgs(optional_args, ca_path, cert_path, key_path):
21 # Treat openvpn arguments
22 if optional_args and optional_args[0] == "--":
23 del optional_args[0]
24 optional_args.append('--ca')
25 optional_args.append(ca_path)
26 optional_args.append('--cert')
27 optional_args.append(cert_path)
28 optional_args.append('--key')
29 optional_args.append(key_path)
30 return optional_args
31
32
33 def getConfig():
34 parser = ArgParser(fromfile_prefix_chars='@',
35 description='Resilient virtual private network application')
36 _ = parser.add_argument
37
38 # General Configuration options
39 _('--ip', default=None, dest='address', action='append', nargs=3,
40 help='Ip address, port and protocol advertised to other vpn nodes')
41 _('--registry', required=True,
42 help="HTTP URL of the discovery peer server,"
43 " with public host (default port: 80)")
44 _('--peers-db-refresh', default=3600, type=int,
45 help='the time (seconds) to wait before refreshing the peers db')
46 _('-l', '--log', default='/var/log',
47 help='Path to vifibnet logs directory')
48 _('-s', '--state', default='/var/lib/vifibnet',
49 help='Path to vifibnet state directory')
50 _('-v', '--verbose', default=0, type=int,
51 help='Defines the verbose level')
52 _('-i', '--interface', action='append', dest='iface_list', default=[],
53 help='Extra interface for LAN discovery')
54
55 # Routing algorithm options
56 _('--hello', type=int, default=15,
57 help='Hello interval for babel, in seconds')
58 _('-w', '--wireless', action='store_true',
59 help='''Set all interfaces to be treated as wireless interfaces
60 for the routing protocol''')
61
62 # Tunnel options
63 _('--pp', nargs=2, action='append',
64 help='Port and protocol to be used by other peers to connect')
65 _('--tunnel-refresh', default=300, type=int,
66 help='time (seconds) to wait before changing the connections')
67 _('--dh', required=True,
68 help='Path to dh file')
69 _('--ca', required=True,
70 help='Path to the certificate authority file')
71 _('--cert', required=True,
72 help='Path to the certificate file')
73 _('--key', required=True,
74 help='Path to the private key file')
75 # args to be removed ?
76 _('--connection-count', default=20, type=int,
77 help='Number of tunnels')
78 _('--refresh-ratio', default=0.05, type=float,
79 help='''The ratio of connections to drop when refreshing the
80 connections''')
81 # Openvpn options
82 _('openvpn_args', nargs=argparse.REMAINDER,
83 help="Common OpenVPN options (e.g. certificates)")
84 return parser.parse_args()
85
86
87 def main():
88 # Get arguments
89 config = getConfig()
90 if not config.pp:
91 config.pp = [['1194', 'udp'], ['1194', 'tcp-server']]
92 manual = bool(config.address)
93 network = utils.networkFromCa(config.ca)
94 internal_ip, prefix = utils.ipFromCert(network, config.cert)
95 openvpn_args = ovpnArgs(config.openvpn_args, config.ca, config.cert,
96 config.key)
97 config.db_path = os.path.join(config.state, 'peers.db')
98
99 # Set logging
100 utils.setupLog(config.verbose)
101
102 logging.trace("Configuration :\n%s" % config)
103
104 # Set global variables
105 tunnel.log = config.log
106 plib.verbose = config.verbose
107
108 # Create and open read_only pipe to get server events
109 logging.info('Creating pipe for server events...')
110 r_pipe, write_pipe = os.pipe()
111 read_pipe = os.fdopen(r_pipe)
112 logging.debug('Pipe created')
113
114 # Init db and tunnels
115 forwarder = None
116 if manual:
117 logging.info('Detected manual external configuration')
118 for c, s in ('udp', 'udp'), ('tcp-client', 'tcp-server'):
119 if len(list(x for x in config.address if x[2] == c)) \
120 < len(list(x for x in config.pp if x[1] == s)):
121 pass # XXX: warn user about probable misconfiguration
122 else:
123 logging.info('Attempting automatic configuration via UPnP...')
124 try:
125 forwarder = upnpigd.Forwarder()
126 config.address = []
127 for port, proto in config.pp:
128 ext = forwarder.AddRule(port, proto)
129 if ext:
130 config.address.append(ext)
131 except upnpigd.NoUPnPDevice:
132 logging.info('No upnp device found')
133
134 peer_db = db.PeerManager(config.db_path, config.registry, config.key,
135 config.peers_db_refresh, config.address, internal_ip, prefix,
136 manual, config.pp, 200)
137 tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args,
138 config.hello, config.tunnel_refresh, config.connection_count,
139 config.refresh_ratio, config.iface_list, network)
140
141 # Launch routing protocol. WARNING : you have to be root to start babeld
142 interface_list = ['vifibnet'] + list(tunnel_manager.free_interface_set) \
143 + config.iface_list
144 router = plib.router(network, internal_ip, interface_list, config.wireless,
145 config.hello, os.path.join(config.state, 'vifibnet.babeld.state'),
146 stdout=os.open(os.path.join(config.log, 'vifibnet.babeld.log'),
147 os.O_WRONLY | os.O_CREAT | os.O_TRUNC), stderr=subprocess.STDOUT)
148
149 # Establish connections
150 server_process = list(plib.server(internal_ip, len(network) + len(prefix),
151 config.connection_count, config.dh, write_pipe, port,
152 proto, config.hello, '--dev', 'vifibnet-%s' % proto, *openvpn_args,
153 stdout=os.open(os.path.join(config.log,
154 'vifibnet.server.%s.log' % (proto,)),
155 os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
156 stderr=subprocess.STDOUT)
157 for port, proto in config.pp)
158 tunnel_manager.refresh()
159
160 # main loop
161 try:
162 try:
163 while True:
164 logging.info('Sleeping ...')
165 nextUpdate = min(tunnel_manager.next_refresh, peer_db.next_refresh)
166 if forwarder != None:
167 nextUpdate = min(nextUpdate, forwarder.next_refresh)
168 nextUpdate = max(0, nextUpdate - time.time())
169
170 ready, tmp1, tmp2 = select.select([read_pipe], [], [], nextUpdate)
171 if ready:
172 peer_db.handle_message(read_pipe.readline())
173 if time.time() >= peer_db.next_refresh:
174 peer_db.refresh()
175 if time.time() >= tunnel_manager.next_refresh:
176 tunnel_manager.refresh()
177 if forwarder != None and time.time() > forwarder.next_refresh:
178 forwarder.refresh()
179 finally:
180 for p in [router] + server_process:
181 try:
182 p.terminate()
183 except:
184 pass
185 try:
186 tunnel_manager.killAll()
187 except:
188 pass
189 except sqlite3.Error:
190 traceback.print_exc()
191 os.rename(config.db_path, config.db_path + '.bak')
192 os.execvp(sys.executable, sys.argv)
193 except KeyboardInterrupt:
194 return 0
195
196 if __name__ == "__main__":
197 main()