vifibnet.py 7.8 KB
Newer Older
Ulysse Beaugnon's avatar
Ulysse Beaugnon committed
1
#!/usr/bin/env python
Guillaume Bury's avatar
Guillaume Bury committed
2
import argparse, errno, os, select, subprocess, sqlite3, time, logging
3
from argparse import ArgumentParser
Guillaume Bury's avatar
Guillaume Bury committed
4
import db, plib, upnpigd, utils, tunnel
5

6

7
class ArgParser(ArgumentParser):
8 9

    def convert_arg_line_to_args(self, arg_line):
10 11
        arg_line = arg_line.split('#')[0].rstrip()
        if arg_line:
12 13 14
            if arg_line.startswith('@'):
                yield arg_line
                return
15 16 17
            for arg in ('--' + arg_line.lstrip('--')).split():
                if arg.strip():
                    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 24 25 26 27
        del optional_args[0]
    optional_args.append('--ca')
    optional_args.append(ca_path)
    optional_args.append('--cert')
    optional_args.append(cert_path)
28 29
    optional_args.append('--key')
    optional_args.append(key_path)
30 31
    return optional_args

32

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

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

    # Routing algorithm options
Guillaume Bury's avatar
Guillaume Bury committed
56
    _('--hello', type=int, default=15,
57 58 59 60 61 62
            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
63 64
    _('--pp', nargs=2, action='append',
            help='Port and protocol to be used by other peers to connect')
65
    _('--tunnel-refresh', default=300, type=int,
Guillaume Bury's avatar
Guillaume Bury committed
66
            help='time (seconds) to wait before changing the connections')
Guillaume Bury's avatar
Guillaume Bury committed
67 68 69 70 71 72
    _('--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')
73 74
    _('--key', required=True,
            help='Path to the private key file')
75
    # args to be removed ?
76 77
    _('--connection-count', default=20, type=int,
            help='Number of tunnels')
Guillaume Bury's avatar
Guillaume Bury committed
78
    _('--refresh-ratio', default=0.05, type=float,
79 80
            help='''The ratio of connections to drop when refreshing the
                    connections''')
Guillaume Bury's avatar
Guillaume Bury committed
81 82 83 84
    # Openvpn options
    _('openvpn_args', nargs=argparse.REMAINDER,
            help="Common OpenVPN options (e.g. certificates)")
    return parser.parse_args()
Ulysse Beaugnon's avatar
Ulysse Beaugnon committed
85

86

87
def main():
88
    # Get arguments
Guillaume Bury's avatar
Guillaume Bury committed
89
    config = getConfig()
90
    if not config.pp:
91
        config.pp = [['1194', 'udp'], ['1194', 'tcp-server']]
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

Guillaume Bury's avatar
Guillaume Bury committed
98 99 100 101 102 103 104 105
    # Set logging
    logging.basicConfig(level=logging.DEBUG,
            format='%(asctime)s : %(message)s',
            datefmt='%d-%m-%Y %H:%M:%S')
    logging.addLevelName(5, 'TRACE')
    logging.trace = lambda *args, **kw: logging.log(5, *args, **kw)
    logging.trace("Configuration :\n%s" % config)

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

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

Guillaume Bury's avatar
Guillaume Bury committed
116
    # Init db and tunnels
Ulysse Beaugnon's avatar
Ulysse Beaugnon committed
117
    forwarder = None
118
    if manual:
Guillaume Bury's avatar
Guillaume Bury committed
119 120 121 122 123
        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)):
                pass # XXX: warn user about probable misconfiguration
124
    else:
Guillaume Bury's avatar
Guillaume Bury committed
125
        logging.info('Attempting automatic configuration via UPnP...')
126
        try:
Ulysse Beaugnon's avatar
Ulysse Beaugnon committed
127 128 129 130 131 132 133
            forwarder = upnpigd.Forwarder()
            config.address = []
            for port, proto in config.pp:
                ext = forwarder.AddRule(port, proto)
                if ext:
                    config.address.append(ext)
        except upnpigd.NoUPnPDevice:
Guillaume Bury's avatar
Guillaume Bury committed
134
            logging.info('No upnp device found')
135

136
    peer_db = db.PeerManager(config.state, config.registry, config.key,
137
            config.peers_db_refresh, config.address, internal_ip, prefix,
138
            manual, config.pp, 200)
139 140
    tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args,
            config.hello, config.tunnel_refresh, config.connection_count,
Guillaume Bury's avatar
Guillaume Bury committed
141
            config.refresh_ratio, config.iface_list, network)
Guillaume Bury's avatar
Guillaume Bury committed
142

143
    # Launch routing protocol. WARNING : you have to be root to start babeld
144 145
    interface_list = ['vifibnet'] + list(tunnel_manager.free_interface_set) \
                     + config.iface_list
146 147 148
    router = plib.router(network, internal_ip, interface_list, config.wireless,
            config.hello, os.path.join(config.state, 'vifibnet.babeld.state'),
            stdout=os.open(os.path.join(config.log, 'vifibnet.babeld.log'),
149
            os.O_WRONLY | os.O_CREAT | os.O_TRUNC), stderr=subprocess.STDOUT)
Guillaume Bury's avatar
Guillaume Bury committed
150

151
   # Establish connections
152
    server_process = list(plib.server(internal_ip, len(network) + len(prefix),
153
        config.connection_count, config.dh, write_pipe, port,
Guillaume Bury's avatar
Guillaume Bury committed
154
        proto, config.hello, '--dev', 'vifibnet-%s' % proto, *openvpn_args,
Guillaume Bury's avatar
Guillaume Bury committed
155 156
        stdout=os.open(os.path.join(config.log,
            'vifibnet.server.%s.log' % (proto,)),
157 158
            os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
        stderr=subprocess.STDOUT)
159
        for port, proto in config.pp)
Guillaume Bury's avatar
Guillaume Bury committed
160
    tunnel_manager.refresh()
Guillaume Bury's avatar
Guillaume Bury committed
161

162 163
    # main loop
    try:
164
        try:
165
            while True:
Guillaume Bury's avatar
Guillaume Bury committed
166
                logging.info('Sleeping ...')
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
                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
193
        db_path = os.path.join(config.state, 'peers.db')
194 195 196
        os.rename(db_path, db_path + '.bak')
        os.execvp(sys.executable, sys.argv)
    except KeyboardInterrupt:
197
        return 0
Guillaume Bury's avatar
wip  
Guillaume Bury committed
198 199 200

if __name__ == "__main__":
    main()