Change protocol to discover addresses of peers to connect to
[re6stnet.git] / re6stnet
1 #!/usr/bin/env python
2 import atexit, os, sys, select, time
3 import argparse, signal, subprocess, sqlite3, logging, traceback
4 from re6st import plib, utils, db, tunnel
5
6 def ovpnArgs(optional_args, ca_path, cert_path, key_path):
7     # Treat openvpn arguments
8     if optional_args and optional_args[0] == "--":
9         del optional_args[0]
10     optional_args.append('--ca')
11     optional_args.append(ca_path)
12     optional_args.append('--cert')
13     optional_args.append(cert_path)
14     optional_args.append('--key')
15     optional_args.append(key_path)
16     return optional_args
17
18
19 def getConfig():
20     parser = utils.ArgParser(fromfile_prefix_chars='@',
21             description='Resilient virtual private network application')
22     _ = parser.add_argument
23
24     # General Configuration options
25     _('--ip',
26             help='IP address advertised to other nodes')
27     _('--registry', required=True,
28             help="HTTP URL of the discovery peer server,"
29                  " with public host (default port: 80)")
30     _('-l', '--log', default='/var/log',
31             help='Path to re6stnet logs directory')
32     _('-s', '--state', default='/var/lib/re6stnet',
33             help='Path to re6stnet state directory')
34     _('-v', '--verbose', default=1, type=int,
35             help='Log level of re6st itself')
36     _('-i', '--interface', action='append', dest='iface_list', default=[],
37             help='Extra interface for LAN discovery')
38
39     # Routing algorithm options
40     _('--babel-pidfile',
41             help='Specify a file to write our process id to')
42     _('--babel-verb', default=0,
43             help='Babel verbosity')
44     _('--hello', type=int, default=15,
45             help='Hello interval for babel, in seconds')
46     _('-w', '--wireless', action='store_true',
47             help='''Set all interfaces to be treated as wireless interfaces
48                     for the routing protocol''')
49
50     # Tunnel options
51     _('--encrypt', action='store_true',
52             help='specify that tunnels should be encrypted')
53     _('--pp', nargs=2, action='append',
54             help='Port and protocol to be used by other peers to connect')
55     _('--dh', required=True,
56             help='Path to dh file')
57     _('--ca', required=True,
58             help='Path to the certificate authority file')
59     _('--cert', required=True,
60             help='Path to the certificate file')
61     _('--key', required=True,
62             help='Path to the private key file')
63     _('--connection-count', default=20, type=int,
64             help='Number of tunnels')
65     _('--tunnel-refresh', default=300, type=int,
66             help='time (seconds) to wait before changing the connections')
67
68     # Openvpn options
69     _('openvpn_args', nargs=argparse.REMAINDER,
70             help="Common OpenVPN options")
71     return parser.parse_args()
72
73
74 def main():
75     # Get arguments
76     config = getConfig()
77     network = utils.networkFromCa(config.ca)
78     internal_ip, prefix = utils.ipFromCert(network, config.cert)
79     openvpn_args = ovpnArgs(config.openvpn_args, config.ca, config.cert,
80                                                  config.key)
81     db_path = os.path.join(config.state, 'peers.db')
82
83     # Set logging
84     utils.setupLog(config.verbose,
85         filename=os.path.join(config.log, 're6stnet.log'))
86
87     logging.trace("Configuration :\n%s" % config)
88
89     # Set global variables
90     plib.log = tunnel.log = config.log
91
92     # Create and open read_only pipe to get server events
93     logging.info('Creating pipe for server events...')
94     r_pipe, write_pipe = os.pipe()
95     read_pipe = os.fdopen(r_pipe)
96
97     signal.signal(signal.SIGHUP, lambda *args: sys.exit(-1))
98
99     # Init db and tunnels
100     address = []
101     if config.pp:
102         pp = [(int(port), proto) for port, proto in config.pp]
103     else:
104         pp = (1194, 'udp'), (1194, 'tcp')
105     ip_changed = lambda ip: [(ip, str(port), proto) for port, proto in pp]
106     forwarder = None
107     if config.ip == 'upnp' or not config.ip:
108         logging.info('Attempting automatic configuration via UPnP...')
109         try:
110             from re6st.upnpigd import Forwarder
111             forwarder = Forwarder()
112         except Exception, e:
113             if config.ip:
114                 raise
115             logging.info("%s: assume we are not NATed", e)
116         else:
117             atexit.register(forwarder.clear)
118             for port, proto in pp:
119                 ip, port = forwarder.addRule(port, proto)
120                 address.append((ip, str(port), proto))
121     elif config.ip != 'any':
122         address = ip_changed(config.ip)
123     if address:
124         ip_changed = None
125
126     peer_db = db.PeerDB(db_path, config.registry, config.key, prefix)
127     tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args,
128         config.hello, config.tunnel_refresh, config.connection_count,
129         config.iface_list, network, prefix, address, ip_changed,
130         config.encrypt)
131
132     # Launch routing protocol. WARNING : you have to be root to start babeld
133     server_tunnels = {}
134     for x in pp:
135         server_tunnels.setdefault('re6stnet-' + x[1], x)
136     interface_list = list(tunnel_manager.free_interface_set) \
137                      + config.iface_list + server_tunnels.keys()
138     subnet = utils.ipFromBin((network + prefix).ljust(128, '0'))
139     router = plib.router(network, subnet, len(prefix) + len(network),
140         interface_list, config.wireless, config.hello, config.babel_verb,
141         config.babel_pidfile, os.path.join(config.state, 'babeld.state'),
142         stdout=os.open(os.path.join(config.log, 'babeld.log'),
143         os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0666), stderr=subprocess.STDOUT)
144
145     # main loop
146     try:
147         try:
148             server_process = []
149             for iface, (port, proto) in server_tunnels.iteritems():
150                 server_process.append(plib.server(iface,
151                     internal_ip if proto == pp[0][1] else None,
152                     len(network) + len(prefix),
153                     config.connection_count, config.dh, write_pipe, port,
154                     proto, config.hello, config.encrypt, *openvpn_args))
155             while True:
156                 next = tunnel_manager.next_refresh
157                 if forwarder:
158                     next = min(next, forwarder.next_refresh)
159                 r = [read_pipe, tunnel_manager.sock]
160                 r = select.select(r, [], [], max(0, next - time.time()))[0]
161                 if read_pipe in r:
162                     tunnel_manager.handleTunnelEvent(read_pipe.readline())
163                 if tunnel_manager.sock in r:
164                     tunnel_manager.handlePeerEvent()
165                 t = time.time()
166                 if t >= tunnel_manager.next_refresh:
167                     tunnel_manager.refresh()
168                 if forwarder and t >= forwarder.next_refresh:
169                     forwarder.refresh()
170         except Exception:
171             f = traceback.format_exception(*sys.exc_info())
172             logging.error('%s%s', f.pop(), ''.join(f))
173             raise
174         finally:
175             router.terminate()
176             for p in server_process:
177                 try:
178                     p.kill()
179                 except:
180                     pass
181             try:
182                 tunnel_manager.killAll()
183             except:
184                 pass
185     except sqlite3.Error:
186         os.rename(db_path, db_path + '.bak')
187         try:
188             sys.exitfunc()
189         finally:
190             os.execvp(sys.argv[0], sys.argv)
191     except KeyboardInterrupt:
192         return 0
193
194 if __name__ == "__main__":
195     main()