Blacklist tunnel connected to us
[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     _('--tunnel-refresh', default=300, type=int,
77             help='time (seconds) to wait before changing the connections')
78
79     # Openvpn options
80     _('openvpn_args', nargs=argparse.REMAINDER,
81             help="Common OpenVPN options")
82     return parser.parse_args()
83
84
85 def main():
86     # Get arguments
87     config = getConfig()
88     if not config.pp:
89         config.pp = [['1194', 'udp'], ['1194', 'tcp-server']]
90     config.pp = list((port, proto, 're6stnet-%s' % proto)
91             for port, proto in config.pp)
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     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                 logging.warning("""Beware: in manual configuration, you
122                         declared less external configurations regarding
123                         protocol %s/%s than you gave internal server
124                         configurations""" % (c, s))
125     else:
126         logging.info('Attempting automatic configuration via UPnP...')
127         try:
128             forwarder = upnpigd.Forwarder()
129             config.address = []
130             for port, proto, _ in config.pp:
131                 ext = forwarder.AddRule(port, proto)
132                 if ext:
133                     config.address.append(ext)
134         except upnpigd.NoUPnPDevice:
135             logging.info('No upnp device found')
136
137     peer_db = db.PeerManager(db_path, config.registry, config.key,
138             config.peers_db_refresh, config.address, internal_ip, prefix,
139             manual, config.pp, 200)
140     tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args,
141             config.hello, config.tunnel_refresh, config.connection_count,
142             config.iface_list, network)
143
144     # Launch routing protocol. WARNING : you have to be root to start babeld
145     interface_list = list(tunnel_manager.free_interface_set) \
146                      + config.iface_list + list(iface
147                              for _, _, iface in config.pp)
148     router = plib.router(network, internal_ip, interface_list, config.wireless,
149             config.hello, os.path.join(config.state, 'babeld.state'),
150             stdout=os.open(os.path.join(config.log, 'babeld.log'),
151             os.O_WRONLY | os.O_CREAT | os.O_TRUNC), stderr=subprocess.STDOUT)
152
153    # Establish connections
154     server_process = list(plib.server(internal_ip, len(network) + len(prefix),
155         config.connection_count, config.dh, write_pipe, port,
156         proto, config.hello, '--dev', iface, *openvpn_args,
157         stdout=os.open(os.path.join(config.log,
158             're6stnet.server.%s.log' % (proto,)),
159             os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
160         stderr=subprocess.STDOUT)
161         for port, proto, iface in config.pp)
162     tunnel_manager.refresh()
163
164     # main loop
165     try:
166         try:
167             while True:
168                 logging.info('Sleeping ...')
169                 nextUpdate = min(tunnel_manager.next_refresh, peer_db.next_refresh)
170                 if forwarder != None:
171                     nextUpdate = min(nextUpdate, forwarder.next_refresh)
172                 nextUpdate = max(0, nextUpdate - time.time())
173
174                 ready, tmp1, tmp2 = select.select([read_pipe], [], [], nextUpdate)
175                 if ready:
176                     peer_db.handle_message(read_pipe.readline())
177                 if time.time() >= peer_db.next_refresh:
178                     peer_db.refresh()
179                 if time.time() >= tunnel_manager.next_refresh:
180                     tunnel_manager.refresh()
181                 if forwarder != None and time.time() > forwarder.next_refresh:
182                     forwarder.refresh()
183         finally:
184             for p in [router] + server_process:
185                 try:
186                     p.terminate()
187                 except:
188                     pass
189             try:
190                 tunnel_manager.killAll()
191             except:
192                 pass
193     except sqlite3.Error:
194         traceback.print_exc()
195         os.rename(db_path, db_path + '.bak')
196         os.execvp(sys.executable, sys.argv)
197     except KeyboardInterrupt:
198         return 0
199
200 if __name__ == "__main__":
201     main()