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