Fix duplicate bootpeer bug
[re6stnet.git] / re6stnet.py
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 _('--refresh-count', default=1, type=int,
77 help='''The number of connections to drop when refreshing the
78 connections''')
79 _('--tunnel-refresh', default=300, type=int,
80 help='time (seconds) to wait before changing the connections')
81
82 # Openvpn options
83 _('openvpn_args', nargs=argparse.REMAINDER,
84 help="Common OpenVPN options (e.g. certificates)")
85 return parser.parse_args()
86
87
88 def main():
89 # Get arguments
90 config = getConfig()
91 if not config.pp:
92 config.pp = [['1194', 'udp'], ['1194', 'tcp-server']]
93 config.pp = list((port, proto, 're6stnet-%s' % proto)
94 for port, proto in config.pp)
95 manual = bool(config.address)
96 network = utils.networkFromCa(config.ca)
97 internal_ip, prefix = utils.ipFromCert(network, config.cert)
98 openvpn_args = ovpnArgs(config.openvpn_args, config.ca, config.cert,
99 config.key)
100 db_path = os.path.join(config.state, 'peers.db')
101
102 # Set logging
103 utils.setupLog(config.verbose)
104
105 logging.trace("Configuration :\n%s" % config)
106
107 # Set global variables
108 tunnel.log = config.log
109 plib.verbose = config.verbose
110
111 # Create and open read_only pipe to get server events
112 logging.info('Creating pipe for server events...')
113 r_pipe, write_pipe = os.pipe()
114 read_pipe = os.fdopen(r_pipe)
115 logging.debug('Pipe created')
116
117 # Init db and tunnels
118 forwarder = None
119 if manual:
120 logging.info('Detected manual external configuration')
121 for c, s in ('udp', 'udp'), ('tcp-client', 'tcp-server'):
122 if len(list(x for x in config.address if x[2] == c)) \
123 < len(list(x for x in config.pp if x[1] == s)):
124 logging.warning("""Beware: in manual configuration, you
125 declared less external configurations regarding
126 protocol %s/%s than you gave internal server
127 configurations""" % (c, s))
128 else:
129 logging.info('Attempting automatic configuration via UPnP...')
130 try:
131 forwarder = upnpigd.Forwarder()
132 config.address = []
133 for port, proto, _ in config.pp:
134 ext = forwarder.AddRule(port, proto)
135 if ext:
136 config.address.append(ext)
137 except upnpigd.NoUPnPDevice:
138 logging.info('No upnp device found')
139
140 peer_db = db.PeerManager(db_path, config.registry, config.key,
141 config.peers_db_refresh, config.address, internal_ip, prefix,
142 manual, config.pp, 200)
143 tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args,
144 config.hello, config.tunnel_refresh, config.connection_count,
145 config.refresh_count, config.iface_list, network)
146
147 # Launch routing protocol. WARNING : you have to be root to start babeld
148 interface_list = list(tunnel_manager.free_interface_set) \
149 + config.iface_list + list(iface
150 for _, _, iface in config.pp)
151 router = plib.router(network, internal_ip, interface_list, config.wireless,
152 config.hello, os.path.join(config.state, 'babeld.state'),
153 stdout=os.open(os.path.join(config.log, 'babeld.log'),
154 os.O_WRONLY | os.O_CREAT | os.O_TRUNC), stderr=subprocess.STDOUT)
155
156 # Establish connections
157 server_process = list(plib.server(internal_ip, len(network) + len(prefix),
158 config.connection_count, config.dh, write_pipe, port,
159 proto, config.hello, '--dev', iface, *openvpn_args,
160 stdout=os.open(os.path.join(config.log,
161 're6stnet.server.%s.log' % (proto,)),
162 os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
163 stderr=subprocess.STDOUT)
164 for port, proto, iface in config.pp)
165 tunnel_manager.refresh()
166
167 # main loop
168 try:
169 try:
170 while True:
171 logging.info('Sleeping ...')
172 nextUpdate = min(tunnel_manager.next_refresh, peer_db.next_refresh)
173 if forwarder != None:
174 nextUpdate = min(nextUpdate, forwarder.next_refresh)
175 nextUpdate = max(0, nextUpdate - time.time())
176
177 ready, tmp1, tmp2 = select.select([read_pipe], [], [], nextUpdate)
178 if ready:
179 peer_db.handle_message(read_pipe.readline())
180 if time.time() >= peer_db.next_refresh:
181 peer_db.refresh()
182 if time.time() >= tunnel_manager.next_refresh:
183 tunnel_manager.refresh()
184 if forwarder != None and time.time() > forwarder.next_refresh:
185 forwarder.refresh()
186 finally:
187 for p in [router] + server_process:
188 try:
189 p.terminate()
190 except:
191 pass
192 try:
193 tunnel_manager.killAll()
194 except:
195 pass
196 except sqlite3.Error:
197 traceback.print_exc()
198 os.rename(db_path, db_path + '.bak')
199 os.execvp(sys.executable, sys.argv)
200 except KeyboardInterrupt:
201 return 0
202
203 if __name__ == "__main__":
204 main()