2 import atexit, errno, logging, os, select, signal
3 import sqlite3, subprocess, sys, time, traceback
4 from re6st import plib, utils, db, tunnel
8 parser = utils.ArgParser(fromfile_prefix_chars='@',
9 description="Resilient virtual private network application.")
10 _ = parser.add_argument
13 help="IP address advertised to other nodes. Special values:\n"
14 "- upnp: force autoconfiguration via UPnP\n"
15 "- any: ask peers our IP\n"
16 " (default: ask peers if UPnP fails)")
17 _('--registry', metavar='URL',
18 help="Public HTTP URL of the registry, for bootstrapping.")
19 _('-l', '--log', default='/var/log/re6stnet',
20 help="Path to the directory used for log files:\n"
21 "- re6stnet.log: log file of re6stnet itself\n"
22 "- babeld.log: log file of router\n"
23 "- <iface>.log: 1 file per spawned OpenVPN\n")
24 _('-s', '--state', default='/var/lib/re6stnet',
25 help="Path to re6stnet state directory:\n"
26 "- peers.db: cache of peer addresses\n"
27 "- babeld.state: see option -S of babeld\n")
28 _('-v', '--verbose', default=1, type=int, metavar='LEVEL',
29 help="Log level of re6stnet itself. 0 disables logging."
30 " Use SIGUSR1 to reopen log."
31 " See also --babel-verb and --verb for logs of spawned processes.")
32 _('-i', '--interface', action='append', dest='iface_list', default=[],
33 help="Extra interface for LAN discovery. Highly recommanded if there"
34 " are other re6st node on the same network segment.")
35 _('-I', '--main-interface', metavar='IFACE',
36 help="Set re6stnet IP on given interface. Any interface not used for"
37 " tunnelling can be chosen. (default: first OpenVPN interface)")
39 _ = parser.add_argument_group('routing').add_argument
40 _('-B', dest='babel_args', metavar='ARG', action='append', default=[],
41 help="Extra arguments to forward to Babel.")
42 _('--babel-pidfile', metavar='PID', default='/var/run/re6st-babeld.pid',
43 help="Specify a file to write our process id to"
44 " (option -I of Babel).")
45 _('--hello', type=int, default=15,
46 help="Hello interval in seconds, for both wired and wireless"
47 " connections. OpenVPN ping-exit option is set to 4 times the"
48 " hello interval. It takes between 3 and 4 times the"
49 " hello interval for Babel to re-establish connection with a"
50 " node for which the direct connection has been cut.")
52 _ = parser.add_argument_group('tunnelling').add_argument
53 _('-O', dest='openvpn_args', metavar='ARG', action='append', default=[],
54 help="Extra arguments to forward to both server and client OpenVPN"
55 " subprocesses. Often used to configure verbosity.")
56 _('--ovpnlog', action='store_true',
57 help="Tell each OpenVPN subprocess to log to a dedicated file.")
58 _('--encrypt', action='store_true',
59 help='Specify that tunnels should be encrypted.')
60 _('--pp', nargs=2, action='append', metavar=('PORT', 'PROTO'),
61 help="Port and protocol to be announced to other peers, ordered by"
62 " preference. For each protocol (either udp or tcp), start one"
63 " openvpn server on the first given port."
64 " (default: --pp 1194 udp --pp 1194 tcp)")
66 help='File containing Diffie-Hellman parameters in .pem format')
67 _('--ca', required=True, help=parser._ca_help)
68 _('--cert', required=True,
69 help="Local peer's signed certificate in .pem format."
70 " Common name defines the allocated prefix in the network.")
71 _('--key', required=True,
72 help="Local peer's private key in .pem format.")
73 _('--client-count', default=10, type=int,
74 help="Number of client tunnels to set up.")
75 _('--max-clients', type=int,
76 help="Maximum number of accepted clients per OpenVPN server. (default:"
77 " client-count * 2, which actually represents the average number"
78 " of tunnels to other peers)")
79 _('--tunnel-refresh', default=300, type=int,
80 help="Interval in seconds between two tunnel refresh: the worst"
81 " tunnel is closed if the number of client tunnels has reached"
82 " its maximum number (client-count).")
83 _('--client', metavar='HOST,PORT,PROTO[;...]',
84 help="Do not run any OpenVPN server, but only 1 OpenVPN client,"
85 " with specified remotes. Any other option not required in this"
86 " mode is ignored (e.g. client-count, max-clients, etc.)")
88 return parser.parse_args()
94 network = utils.networkFromCa(config.ca)
95 prefix = utils.binFromSubnet(utils.subnetFromCert(config.cert))
96 config.openvpn_args += (
98 '--cert', config.cert,
100 # TODO: verify certificates (should we moved to M2Crypto ?)
103 utils.setupLog(config.verbose, os.path.join(config.log, 're6stnet.log'))
105 logging.trace("Configuration:\n%r", config)
106 utils.makedirs(config.state)
107 db_path = os.path.join(config.state, 'peers.db')
109 plib.ovpn_log = config.log
111 signal.signal(signal.SIGHUP, lambda *args: sys.exit(-1))
112 signal.signal(signal.SIGTERM, lambda *args: sys.exit())
114 if config.max_clients is None:
115 config.max_clients = config.client_count * 2
120 config.babel_args.append('re6stnet')
121 elif config.max_clients:
123 pp = [(int(port), proto) for port, proto in config.pp]
125 pp = (1194, 'udp'), (1194, 'tcp')
126 ip_changed = lambda ip: [(ip, str(port), proto) for port, proto in pp]
128 if config.ip == 'upnp' or not config.ip:
129 logging.info('Attempting automatic configuration via UPnP...')
131 from re6st.upnpigd import Forwarder
132 forwarder = Forwarder()
136 logging.info("%s: assume we are not NATed", e)
138 atexit.register(forwarder.clear)
139 for port, proto in pp:
140 ip, port = forwarder.addRule(port, proto)
141 address.append((ip, str(port), proto))
142 elif config.ip != 'any':
143 address = ip_changed(config.ip)
147 server_tunnels.setdefault('re6stnet-' + x[1], x)
150 if not getattr(config, arg):
151 sys.exit("error: argument --%s is required" % arg)
152 def ip(object, *args):
153 args = ['ip', object, 'add'] + list(args)
154 r = subprocess.call(args)
158 cleanup.append(lambda: subprocess.call(args))
161 subnet = network + prefix
162 my_ip = '%s/%s' % (utils.ipFromBin(subnet, '1'), len(subnet))
164 # Init db and tunnels
165 tunnel_interfaces = server_tunnels.keys()
166 timeout = 4 * config.hello
167 if config.client_count and not config.client:
169 # Create and open read_only pipe to get server events
170 r_pipe, write_pipe = os.pipe()
171 read_pipe = os.fdopen(r_pipe)
172 peer_db = db.PeerDB(db_path, config.registry, config.key, prefix)
173 tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db,
174 config.openvpn_args, timeout, config.tunnel_refresh,
175 config.client_count, config.iface_list, network, prefix,
176 address, ip_changed, config.encrypt)
177 tunnel_interfaces += tunnel_manager.free_interface_set
179 tunnel_manager = write_pipe = None
181 config.babel_args += config.iface_list
182 router = plib.router(network, utils.ipFromBin(subnet), len(subnet),
183 config.hello, os.path.join(config.log, 'babeld.log'),
184 os.path.join(config.state, 'babeld.state'),
185 config.babel_pidfile, tunnel_interfaces, *config.babel_args)
189 my_network = "%s/%u" % (utils.ipFromBin(network), len(network))
190 ip('route', 'unreachable', my_network, 'proto', 'static')
191 # prepare persistent interfaces
193 cleanup.append(plib.client('re6stnet', config.client,
195 '--up', '%s %s' % (plib.ovpn_server, None
196 if config.main_interface else my_ip),
197 '--ping-restart', str(timeout),
198 *config.openvpn_args).kill)
201 for iface, (port, proto) in server_tunnels.iteritems():
202 cleanup.append(plib.server(iface, None
203 if config.main_interface or proto != pp[0][1]
204 else my_ip, config.max_clients, config.dh, write_pipe,
205 port, proto, config.encrypt,
206 '--ping-exit', str(timeout), *config.openvpn_args).kill)
207 elif config.iface_list and not config.main_interface:
208 config.main_interface = config.iface_list[0]
210 sys.exit("--client, --interface or --main-interface required"
211 " when --max-clients is 0")
213 if config.main_interface:
214 ip('addr', my_ip, 'dev', config.main_interface)
217 if tunnel_manager is None:
218 sys.exit(os.WEXITSTATUS(os.wait()[1]))
219 cleanup.append(tunnel_manager.killAll)
221 next = tunnel_manager.next_refresh
223 next = min(next, forwarder.next_refresh)
224 r = [read_pipe, tunnel_manager.sock]
226 r = select.select(r, [], [], max(0, next - time.time()))[0]
227 except select.error as e:
228 if e.args[0] != errno.EINTR:
232 tunnel_manager.handleTunnelEvent(read_pipe.readline())
233 if tunnel_manager.sock in r:
234 tunnel_manager.handlePeerEvent()
236 if t >= tunnel_manager.next_refresh:
237 tunnel_manager.refresh()
238 if forwarder and t >= forwarder.next_refresh:
242 for cleanup in cleanup:
247 except sqlite3.Error:
248 logging.exception("Restarting with empty cache")
249 os.rename(db_path, db_path + '.bak')
253 os.execvp(sys.argv[0], sys.argv)
254 except KeyboardInterrupt:
257 f = traceback.format_exception(*sys.exc_info())
258 logging.error('%s%s', f.pop(), ''.join(f))
261 if __name__ == "__main__":