re6stnet 7.74 KB
Newer Older
Ulysse Beaugnon's avatar
Ulysse Beaugnon committed
1
#!/usr/bin/env python
2 3
import os, sys, select, time
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 12
        arg_line = arg_line.split('#')[0].rstrip()
        if arg_line:
13 14 15
            if arg_line.startswith('@'):
                yield arg_line
                return
16 17 18
            for arg in ('--' + arg_line.lstrip('--')).split():
                if arg.strip():
                    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 25 26 27 28
        del optional_args[0]
    optional_args.append('--ca')
    optional_args.append(ca_path)
    optional_args.append('--cert')
    optional_args.append(cert_path)
29 30
    optional_args.append('--key')
    optional_args.append(key_path)
31 32
    return optional_args

33

Guillaume Bury's avatar
Guillaume Bury committed
34
def getConfig():
35
    parser = ArgParser(fromfile_prefix_chars='@',
Guillaume Bury's avatar
Guillaume Bury committed
36 37
            description='Resilient virtual private network application')
    _ = parser.add_argument
38 39 40 41

    # General Configuration options
    _('--ip', default=None, dest='address', action='append', nargs=3,
            help='Ip address, port and protocol advertised to other vpn nodes')
42 43 44
    _('--registry', required=True,
            help="HTTP URL of the discovery peer server,"
                 " with public host (default port: 80)")
45 46
    _('--peers-db-refresh', default=3600, type=int,
            help='the time (seconds) to wait before refreshing the peers db')
47
    _('-l', '--log', default='/var/log',
48 49 50
            help='Path to re6stnet logs directory')
    _('-s', '--state', default='/var/lib/re6stnet',
            help='Path to re6stnet state directory')
51
    _('-v', '--verbose', default=0, type=int,
52
            help='Defines the verbose level')
53 54
    _('-i', '--interface', action='append', dest='iface_list', default=[],
            help='Extra interface for LAN discovery')
55 56

    # Routing algorithm options
Guillaume Bury's avatar
Guillaume Bury committed
57
    _('--hello', type=int, default=15,
58 59 60 61 62 63
            help='Hello interval for babel, in seconds')
    _('-w', '--wireless', action='store_true',
            help='''Set all interfaces to be treated as wireless interfaces
                    for the routing protocol''')

    # Tunnel options
64 65
    _('--pp', nargs=2, action='append',
            help='Port and protocol to be used by other peers to connect')
Guillaume Bury's avatar
Guillaume Bury committed
66 67 68 69 70 71
    _('--dh', required=True,
            help='Path to dh file')
    _('--ca', required=True,
            help='Path to the certificate authority file')
    _('--cert', required=True,
            help='Path to the certificate file')
72 73
    _('--key', required=True,
            help='Path to the private key file')
74 75
    _('--connection-count', default=20, type=int,
            help='Number of tunnels')
76 77 78
    _('--tunnel-refresh', default=300, type=int,
            help='time (seconds) to wait before changing the connections')

Guillaume Bury's avatar
Guillaume Bury committed
79 80
    # Openvpn options
    _('openvpn_args', nargs=argparse.REMAINDER,
Ulysse Beaugnon's avatar
Ulysse Beaugnon committed
81
            help="Common OpenVPN options")
Guillaume Bury's avatar
Guillaume Bury committed
82
    return parser.parse_args()
Ulysse Beaugnon's avatar
Ulysse Beaugnon committed
83

84

85
def main():
86
    # Get arguments
Guillaume Bury's avatar
Guillaume Bury committed
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)
Guillaume Bury's avatar
Guillaume Bury committed
91
            for port, proto in config.pp)
92
    manual = bool(config.address)
Guillaume Bury's avatar
Guillaume Bury committed
93
    network = utils.networkFromCa(config.ca)
94
    internal_ip, prefix = utils.ipFromCert(network, config.cert)
95 96
    openvpn_args = ovpnArgs(config.openvpn_args, config.ca, config.cert,
                                                 config.key)
Guillaume Bury's avatar
Guillaume Bury committed
97
    db_path = os.path.join(config.state, 'peers.db')
Guillaume Bury's avatar
Guillaume Bury committed
98

99
    # Set logging
Guillaume Bury's avatar
Guillaume Bury committed
100 101
    utils.setupLog(config.verbose)

102 103
    logging.trace("Configuration :\n%s" % config)

Guillaume Bury's avatar
Guillaume Bury committed
104 105
    # Set global variables
    tunnel.log = config.log
106
    plib.verbose = config.verbose
107

Guillaume Bury's avatar
Guillaume Bury committed
108
    # Create and open read_only pipe to get server events
109
    logging.info('Creating pipe for server events...')
110 111
    r_pipe, write_pipe = os.pipe()
    read_pipe = os.fdopen(r_pipe)
112
    logging.debug('Pipe created')
113

Guillaume Bury's avatar
Guillaume Bury committed
114
    # Init db and tunnels
115
    forwarder = None
116
    if manual:
117 118 119 120
        logging.info('Detected manual external configuration')
        for c, s in ('udp', 'udp'), ('tcp-client', 'tcp-server'):
            if len(list(x for x in config.address if x[2] == c)) \
             < len(list(x for x in config.pp if x[1] == s)):
121 122 123 124
                logging.warning("""Beware: in manual configuration, you
                        declared less external configurations regarding
                        protocol %s/%s than you gave internal server
                        configurations""" % (c, s))
125
    else:
126
        logging.info('Attempting automatic configuration via UPnP...')
127
        try:
128 129
            forwarder = upnpigd.Forwarder()
            config.address = []
Guillaume Bury's avatar
Guillaume Bury committed
130
            for port, proto, _ in config.pp:
131 132 133 134
                ext = forwarder.AddRule(port, proto)
                if ext:
                    config.address.append(ext)
        except upnpigd.NoUPnPDevice:
135
            logging.info('No upnp device found')
136

Guillaume Bury's avatar
Guillaume Bury committed
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 141
    tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args,
            config.hello, config.tunnel_refresh, config.connection_count,
Ulysse Beaugnon's avatar
Ulysse Beaugnon committed
142 143
            config.iface_list, network, prefix)
    peer_db.tunnel_manager = tunnel_manager
Guillaume Bury's avatar
Guillaume Bury committed
144

145
    # Launch routing protocol. WARNING : you have to be root to start babeld
Guillaume Bury's avatar
Guillaume Bury committed
146 147 148
    interface_list = list(tunnel_manager.free_interface_set) \
                     + config.iface_list + list(iface
                             for _, _, iface in config.pp)
Ulysse Beaugnon's avatar
Ulysse Beaugnon committed
149
    router = plib.router(network, internal_ip, interface_list, config.wireless,
Guillaume Bury's avatar
Guillaume Bury committed
150 151
            config.hello, os.path.join(config.state, 'babeld.state'),
            stdout=os.open(os.path.join(config.log, 'babeld.log'),
152
            os.O_WRONLY | os.O_CREAT | os.O_TRUNC), stderr=subprocess.STDOUT)
Guillaume Bury's avatar
Guillaume Bury committed
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,
Guillaume Bury's avatar
Guillaume Bury committed
157
        proto, config.hello, '--dev', iface, *openvpn_args,
158
        stdout=os.open(os.path.join(config.log,
159
            're6stnet.server.%s.log' % (proto,)),
160 161
            os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
        stderr=subprocess.STDOUT)
Guillaume Bury's avatar
Guillaume Bury committed
162
        for port, proto, iface in config.pp)
Guillaume Bury's avatar
Guillaume Bury committed
163

164 165
    # main loop
    try:
166
        try:
167
            while True:
168
                logging.info('Sleeping ...')
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
                nextUpdate = min(tunnel_manager.next_refresh, peer_db.next_refresh)
                if forwarder != None:
                    nextUpdate = min(nextUpdate, forwarder.next_refresh)
                nextUpdate = max(0, nextUpdate - time.time())

                ready, tmp1, tmp2 = select.select([read_pipe], [], [], nextUpdate)
                if ready:
                    peer_db.handle_message(read_pipe.readline())
                if time.time() >= peer_db.next_refresh:
                    peer_db.refresh()
                if time.time() >= tunnel_manager.next_refresh:
                    tunnel_manager.refresh()
                if forwarder != None and time.time() > forwarder.next_refresh:
                    forwarder.refresh()
        finally:
            for p in [router] + server_process:
                try:
                    p.terminate()
                except:
                    pass
            try:
                tunnel_manager.killAll()
            except:
                pass
    except sqlite3.Error:
        traceback.print_exc()
Guillaume Bury's avatar
Guillaume Bury committed
195
        os.rename(db_path, db_path + '.bak')
196 197
        os.execvp(sys.executable, sys.argv)
    except KeyboardInterrupt:
198
        return 0
Guillaume Bury's avatar
Guillaume Bury committed
199 200 201

if __name__ == "__main__":
    main()