Populate has been removed
[re6stnet.git] / re6stnet
1 #!/usr/bin/env python
2 import os, sys, select, time, socket
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     _('--encrypt', action='store_true',
65             help='specify that tunnels should be encrypted')
66     _('--pp', nargs=2, action='append',
67             help='Port and protocol to be used by other peers to connect')
68     _('--dh', required=True,
69             help='Path to dh file')
70     _('--ca', required=True,
71             help='Path to the certificate authority file')
72     _('--cert', required=True,
73             help='Path to the certificate file')
74     _('--key', required=True,
75             help='Path to the private key file')
76     _('--connection-count', default=20, type=int,
77             help='Number of tunnels')
78     _('--tunnel-refresh', default=300, type=int,
79             help='time (seconds) to wait before changing the connections')
80
81     # Openvpn options
82     _('openvpn_args', nargs=argparse.REMAINDER,
83             help="Common OpenVPN options")
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     config.pp = list((port, proto, 're6stnet-%s' % proto)
93             for port, proto in config.pp)
94     manual = bool(config.address)
95     network = utils.networkFromCa(config.ca)
96     internal_ip, prefix = utils.ipFromCert(network, config.cert)
97     openvpn_args = ovpnArgs(config.openvpn_args, config.ca, config.cert,
98                                                  config.key)
99     db_path = os.path.join(config.state, 'peers.db')
100
101     # Set logging
102     utils.setupLog(config.verbose)
103
104     logging.trace("Configuration :\n%s" % config)
105
106     # Set global variables
107     tunnel.log = config.log
108     plib.verbose = config.verbose
109
110     # Create and open read_only pipe to get server events
111     logging.info('Creating pipe for server events...')
112     r_pipe, write_pipe = os.pipe()
113     read_pipe = os.fdopen(r_pipe)
114     logging.debug('Pipe created')
115
116     # Init db and tunnels
117     forwarder = None
118     if manual:
119         logging.info('Detected manual external configuration')
120         for c, s in ('udp', 'udp'), ('tcp-client', 'tcp-server'):
121             if len(list(x for x in config.address if x[2] == c)) \
122              < len(list(x for x in config.pp if x[1] == s)):
123                 logging.warning("""Beware: in manual configuration, you
124                         declared less external configurations regarding
125                         protocol %s/%s than you gave internal server
126                         configurations""" % (c, s))
127     else:
128         logging.info('Attempting automatic configuration via UPnP...')
129         try:
130             forwarder = upnpigd.Forwarder()
131             config.address = []
132             for port, proto, _ in config.pp:
133                 ext = forwarder.AddRule(port, proto)
134                 if ext:
135                     config.address.append(ext)
136         except upnpigd.NoUPnPDevice:
137             logging.info('No upnp device found')
138
139     peer_db = db.PeerManager(db_path, config.registry, config.key,
140             config.peers_db_refresh, config.address, internal_ip, prefix,
141             manual, config.pp, 200)
142     tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args,
143             config.hello, config.tunnel_refresh, config.connection_count,
144             config.iface_list, network, prefix, 2, config.encrypt)
145     peer_db.tunnel_manager = tunnel_manager
146
147     # Create the socket to listen on
148     sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
149     sock.bind(('::', 326))
150     socket_file = sock.makefile()
151
152     # Launch routing protocol. WARNING : you have to be root to start babeld
153     interface_list = list(tunnel_manager.free_interface_set) \
154                      + config.iface_list + list(iface
155                              for _, _, iface in config.pp)
156     router = plib.router(network, internal_ip, interface_list, config.wireless,
157             config.hello, os.path.join(config.state, 'babeld.state'),
158             stdout=os.open(os.path.join(config.log, 'babeld.log'),
159             os.O_WRONLY | os.O_CREAT | os.O_TRUNC), stderr=subprocess.STDOUT)
160
161    # Establish connections
162     server_process = []
163     server_ip = internal_ip
164     for port, proto, iface in config.pp:
165         server_process.append(plib.server(server_ip, len(network) + len(prefix),
166         config.connection_count, config.dh, write_pipe, port,
167         proto, config.hello, config.encrypt, '--dev', iface, *openvpn_args,
168         stdout=os.open(os.path.join(config.log,
169             're6stnet.server.%s.log' % (proto,)),
170             os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
171         stderr=subprocess.STDOUT))
172         server_ip = ''
173
174     # main loop
175     try:
176         try:
177             while True:
178                 logging.info('Sleeping ...')
179                 nextUpdate = min(tunnel_manager.next_refresh, peer_db.next_refresh)
180                 if forwarder != None:
181                     nextUpdate = min(nextUpdate, forwarder.next_refresh)
182                 nextUpdate = max(0, nextUpdate - time.time())
183                 ready, tmp1, tmp2 = select.select([read_pipe, socket_file], [], [], nextUpdate)
184                 if read_pipe in ready:
185                     peer_db.handle_message(read_pipe.readline())
186                 if time.time() >= peer_db.next_refresh:
187                     peer_db.refresh()
188                 if time.time() >= tunnel_manager.next_refresh:
189                     tunnel_manager.refresh()
190                 if forwarder != None and time.time() > forwarder.next_refresh:
191                     forwarder.refresh()
192                 if socket_file in ready:
193                     peer_db.readSocket(socket_file.readline())
194         finally:
195             for p in [router] + server_process:
196                 try:
197                     p.terminate()
198                 except:
199                     pass
200             try:
201                 tunnel_manager.killAll()
202             except:
203                 pass
204     except sqlite3.Error:
205         traceback.print_exc()
206         os.rename(db_path, db_path + '.bak')
207         os.execvp(sys.executable, sys.argv)
208     except KeyboardInterrupt:
209         return 0
210
211 if __name__ == "__main__":
212     main()