Put all annexes python files into a subfolder
[re6stnet.git] / re6stnet.py
1 #!/usr/bin/env python
2 import argparse, errno, os, select, subprocess, sqlite3, time, logging
3 from argparse import ArgumentParser
4 from re6st import plib, utils, db, upnpigd, 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 re6stnet logs directory')
48 _('-s', '--state', default='/var/lib/re6stnet',
49 help='Path to re6stnet 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 _('--connection-count', default=20, type=int,
76 help='Number of tunnels')
77 _('--refresh-count', default=1, type=int,
78 help='''The number of connections to drop when refreshing the
79 connections''')
80 # Openvpn options
81 _('openvpn_args', nargs=argparse.REMAINDER,
82 help="Common OpenVPN options (e.g. certificates)")
83 return parser.parse_args()
84
85
86 def main():
87 # Get arguments
88 config = getConfig()
89 if not config.pp:
90 config.pp = [['1194', 'udp'], ['1194', 'tcp-server']]
91 config.pp = list((port, proto, 're6stnet-%s' % proto)
92 for port, proto in config.pp)
93 manual = bool(config.address)
94 network = utils.networkFromCa(config.ca)
95 internal_ip, prefix = utils.ipFromCert(network, config.cert)
96 openvpn_args = ovpnArgs(config.openvpn_args, config.ca, config.cert,
97 config.key)
98 db_path = os.path.join(config.state, 'peers.db')
99
100 # Set logging
101 utils.setupLog(config.verbose)
102
103 logging.trace("Configuration :\n%s" % config)
104
105 # Set global variables
106 tunnel.log = config.log
107 plib.verbose = config.verbose
108
109 # Create and open read_only pipe to get server events
110 logging.info('Creating pipe for server events...')
111 r_pipe, write_pipe = os.pipe()
112 read_pipe = os.fdopen(r_pipe)
113 logging.debug('Pipe created')
114
115 # Init db and tunnels
116 forwarder = None
117 if manual:
118 logging.info('Detected manual external configuration')
119 for c, s in ('udp', 'udp'), ('tcp-client', 'tcp-server'):
120 if len(list(x for x in config.address if x[2] == c)) \
121 < len(list(x for x in config.pp if x[1] == s)):
122 logging.warning("""Beware: in manual configuration, you
123 declared less external configurations regarding
124 protocol %s/%s than you gave internal server
125 configurations""" % (c, s))
126 else:
127 logging.info('Attempting automatic configuration via UPnP...')
128 try:
129 forwarder = upnpigd.Forwarder()
130 config.address = []
131 for port, proto, _ in config.pp:
132 ext = forwarder.AddRule(port, proto)
133 if ext:
134 config.address.append(ext)
135 except upnpigd.NoUPnPDevice:
136 logging.info('No upnp device found')
137
138 peer_db = db.PeerManager(db_path, config.registry, config.key,
139 config.peers_db_refresh, config.address, internal_ip, prefix,
140 manual, config.pp, 200)
141 tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args,
142 config.hello, config.tunnel_refresh, config.connection_count,
143 config.refresh_count, config.iface_list, network)
144
145 # Launch routing protocol. WARNING : you have to be root to start babeld
146 interface_list = list(tunnel_manager.free_interface_set) \
147 + config.iface_list + list(iface
148 for _, _, iface in config.pp)
149 router = plib.router(network, internal_ip, interface_list, config.wireless,
150 config.hello, os.path.join(config.state, 'babeld.state'),
151 stdout=os.open(os.path.join(config.log, 'babeld.log'),
152 os.O_WRONLY | os.O_CREAT | os.O_TRUNC), stderr=subprocess.STDOUT)
153
154 # Establish connections
155 server_process = list(plib.server(internal_ip, len(network) + len(prefix),
156 config.connection_count, config.dh, write_pipe, port,
157 proto, config.hello, '--dev', iface, *openvpn_args,
158 stdout=os.open(os.path.join(config.log,
159 're6stnet.server.%s.log' % (proto,)),
160 os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
161 stderr=subprocess.STDOUT)
162 for port, proto, iface in config.pp)
163 tunnel_manager.refresh()
164
165 # main loop
166 try:
167 try:
168 while True:
169 logging.info('Sleeping ...')
170 nextUpdate = min(tunnel_manager.next_refresh, peer_db.next_refresh)
171 if forwarder != None:
172 nextUpdate = min(nextUpdate, forwarder.next_refresh)
173 nextUpdate = max(0, nextUpdate - time.time())
174
175 ready, tmp1, tmp2 = select.select([read_pipe], [], [], nextUpdate)
176 if ready:
177 peer_db.handle_message(read_pipe.readline())
178 if time.time() >= peer_db.next_refresh:
179 peer_db.refresh()
180 if time.time() >= tunnel_manager.next_refresh:
181 tunnel_manager.refresh()
182 if forwarder != None and time.time() > forwarder.next_refresh:
183 forwarder.refresh()
184 finally:
185 for p in [router] + server_process:
186 try:
187 p.terminate()
188 except:
189 pass
190 try:
191 tunnel_manager.killAll()
192 except:
193 pass
194 except sqlite3.Error:
195 traceback.print_exc()
196 os.rename(db_path, db_path + '.bak')
197 os.execvp(sys.executable, sys.argv)
198 except KeyboardInterrupt:
199 return 0
200
201 if __name__ == "__main__":
202 main()