Port forwarding is now correctly refreshed
[re6stnet.git] / vifibnet.py
1 #!/usr/bin/env python
2 import argparse, errno, os, select, subprocess, time
3 from argparse import ArgumentParser
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 arg_line = arg_line.split('#')[0].rstrip()
11 if arg_line:
12 if arg_line.startswith('@'):
13 yield arg_line
14 return
15 for arg in ('--' + arg_line.lstrip('--')).split():
16 if arg.strip():
17 yield arg
18
19
20 def ovpnArgs(optional_args, ca_path, cert_path):
21 # Treat openvpn arguments
22 if optional_args[0] == "--":
23 del optional_args[0]
24 optional_args.append('--ca')
25 optional_args.append(ca_path)
26 optional_args.append('--cert')
27 optional_args.append(cert_path)
28 return optional_args
29
30
31 def getConfig():
32 parser = ArgParser(fromfile_prefix_chars='@',
33 description='Resilient virtual private network application')
34 _ = parser.add_argument
35
36 # General Configuration options
37 _('--ip', default=None, dest='address', action='append', nargs=3,
38 help='Ip address, port and protocol advertised to other vpn nodes')
39 _('--peers-db-refresh', default=3600, type=int,
40 help='the time (seconds) to wait before refreshing the peers db')
41 _('-l', '--log', default='/var/log',
42 help='Path to vifibnet logs directory')
43 _('-s', '--state', default='/var/lib/vifibnet',
44 help='Path to VPN state directory')
45 _('-v', '--verbose', default=0, type=int,
46 help='Defines the verbose level')
47 _('-i', '--interface', action='append', dest='iface_list', default=[],
48 help='Extra interface for LAN discovery')
49 _('--server', required=True,
50 help="VPN address of the discovery peer server")
51 _('--server-port', required=True, type=int,
52 help="VPN port of the discovery peer server")
53
54 # Routing algorithm options
55 _('--hello', type=int, default=15,
56 help='Hello interval for babel, in seconds')
57 _('-w', '--wireless', action='store_true',
58 help='''Set all interfaces to be treated as wireless interfaces
59 for the routing protocol''')
60
61 # Tunnel options
62 _('--pp', nargs=2, action='append',
63 help='Port and protocol to be used by other peers to connect')
64 _('--tunnel-refresh', default=300, type=int,
65 help='the time (seconds) to wait before changing the connections')
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 # args to be removed ?
73 _('--connection-count', default=20, type=int,
74 help='Number of tunnels')
75 _('--refresh-rate', default=0.05, type=float,
76 help='''The ratio of connections to drop when refreshing the
77 connections''')
78 # Openvpn options
79 _('openvpn_args', nargs=argparse.REMAINDER,
80 help="Common OpenVPN options (e.g. certificates)")
81 return parser.parse_args()
82
83
84 def main():
85 # Get arguments
86 config = getConfig()
87 if not config.pp:
88 config.pp = [['1194', 'udp']]
89 manual = bool(config.address)
90 network = utils.networkFromCa(config.ca)
91 internal_ip, prefix = utils.ipFromCert(network, config.cert)
92 openvpn_args = ovpnArgs(config.openvpn_args, config.ca, config.cert)
93
94 # Set global variables
95 tunnel.log = config.log
96 utils.verbose = plib.verbose = config.verbose
97
98 utils.log("Configuration :\n" + str(config), 5)
99
100 # Create and open read_only pipe to get server events
101 utils.log('Creating pipe for server events...', 3)
102 r_pipe, write_pipe = os.pipe()
103 read_pipe = os.fdopen(r_pipe)
104 utils.log('Pipe created', 5)
105
106 # Init db and tunnels
107 forwarder = None
108 if manual:
109 utils.log('Detected manual external configuration', 3)
110 else:
111 utils.log('Attempting automatic configuration via UPnP...', 4)
112 try:
113 forwarder = upnpigd.Forwarder()
114 config.address = []
115 for port, proto in config.pp:
116 ext = forwarder.AddRule(port, proto)
117 if ext:
118 config.address.append(ext)
119 except upnpigd.NoUPnPDevice:
120 utils.log('No upnp device found', 4)
121
122 peer_db = db.PeerManager(config.state, config.server, config.server_port,
123 config.peers_db_refresh, config.address, internal_ip, prefix,
124 manual, config.pp, 200)
125 tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args,
126 config.hello, config.tunnel_refresh, config.connection_count,
127 config.refresh_rate, config.iface_list, network)
128
129 # Launch routing protocol. WARNING : you have to be root to start babeld
130 interface_list = ['vifibnet'] + list(tunnel_manager.free_interface_set) \
131 + config.iface_list
132 router = plib.router(network, internal_ip, interface_list, config.wireless,
133 config.hello, os.path.join(config.state, 'vifibnet.babeld.state'),
134 stdout=os.open(os.path.join(config.log, 'vifibnet.babeld.log'),
135 os.O_WRONLY | os.O_CREAT | os.O_TRUNC), stderr=subprocess.STDOUT)
136
137 # Establish connections
138 server_process = list(plib.server(internal_ip, len(network) + len(prefix),
139 config.connection_count, config.dh, write_pipe, port,
140 proto, config.hello, '--dev', 'vifibnet', *openvpn_args,
141 stdout=os.open(os.path.join(config.log,
142 'vifibnet.server.%s.log' % (proto,)),
143 os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
144 stderr=subprocess.STDOUT)
145 for port, proto in config.pp)
146 tunnel_manager.refresh()
147
148 # main loop
149 try:
150 while True:
151 utils.log('Sleeping ...', 2)
152 nextUpdate = min(tunnel_manager.next_refresh, peer_db.next_refresh)
153 if forwarder != None:
154 nextUpdate = min(nextUpdate, forwarder.next_refresh)
155 nextUpdate = max(0, nextUpdate - time.time())
156
157 ready, tmp1, tmp2 = select.select([read_pipe], [], [], nextUpdate)
158 if ready:
159 peer_db.handle_message(read_pipe.readline())
160 if time.time() >= peer_db.next_refresh:
161 peer_db.refresh()
162 if time.time() >= tunnel_manager.next_refresh:
163 tunnel_manager.refresh()
164 if forwarder != None and time.time() > forwarder.next_refresh:
165 forwarder.refresh()
166 except KeyboardInterrupt:
167 return 0
168
169 if __name__ == "__main__":
170 main()