Commit b1e97826 authored by Julien Muchembled's avatar Julien Muchembled

Bugfixes

parent b3cb14da
...@@ -33,6 +33,7 @@ request. ...@@ -33,6 +33,7 @@ request.
--port port --port port
The port on which the server will listen The port on which the server will listen
Default: 80
--db path --db path
Path to the server Database file. A new DB file will be created Path to the server Database file. A new DB file will be created
......
...@@ -72,10 +72,10 @@ Generic options ...@@ -72,10 +72,10 @@ Generic options
file ( since it is an optioin, it must be alone on the line ). file ( since it is an optioin, it must be alone on the line ).
--ip address port proto --ip address port proto
Specify connection information to be advertised to other nodes. IP address advertised to other nodes. Special values:
address MUST be a ipv4 address since as of now openvpn does not - upnp: force autoconfiguration via UPnP
support ipv6 addresses. - any: ask peers our IP
Proto should be either udp or tcp-client Default: ask peers if UPnP fails
-l, ``--log`` `directory` -l, ``--log`` `directory`
Path to the directory used for log files. Will create one file Path to the directory used for log files. Will create one file
...@@ -103,6 +103,12 @@ Generic options ...@@ -103,6 +103,12 @@ Generic options
Babel options Babel options
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
--babel-pidfile
Specify a file to write our process id to (option -I of Babel).
--babel-verb
Babel verbosity (option -d of Babel).
-i, ``--interface`` `interface` -i, ``--interface`` `interface`
Give one interface name for each use of the argument. The interface Give one interface name for each use of the argument. The interface
will be used to detect other nodes on the local network. will be used to detect other nodes on the local network.
...@@ -135,13 +141,11 @@ Tunnel & Peers options ...@@ -135,13 +141,11 @@ Tunnel & Peers options
Default : 3600 ( 1 hour ) Default : 3600 ( 1 hour )
--pp port proto --pp port proto
Port and protocol used by the openvpn server(s). Start one openvpn Port and protocol to be announced to other peers, ordered by
server for each couple port/protocol specified. preference. For each protocol, start one openvpn server on the
Additionally, if no external configuration is given in the command first given port.
line, re6stnet will attempt to forward a port with upnp for each Protocols: udp, tcp
couple port/proto given. Default : --pp 1194 udp --pp 1194 tcp
Protocols should be either udp or tcp-server.
Default : (1194, udp), (1194, tcp-server)
--tunnel-refresh duration --tunnel-refresh duration
Interval in seconds between two tunnel refresh. Refreshing tunnels Interval in seconds between two tunnel refresh. Refreshing tunnels
......
#!/usr/bin/env python #!/usr/bin/env python
import argparse, random, select, smtplib, sqlite3, string, socket import random, select, smtplib, sqlite3, string, socket
import subprocess, time, threading, traceback, errno, logging, os, xmlrpclib import subprocess, time, threading, traceback, errno, logging, os, xmlrpclib
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
from email.mime.text import MIMEText from email.mime.text import MIMEText
...@@ -15,18 +15,18 @@ SOL_IPV6 = 41 ...@@ -15,18 +15,18 @@ SOL_IPV6 = 41
class RequestHandler(SimpleXMLRPCRequestHandler): class RequestHandler(SimpleXMLRPCRequestHandler):
def address_string(self):
# Workaround for http://bugs.python.org/issue6085
return self.client_address[0]
def _dispatch(self, method, params): def _dispatch(self, method, params):
logging.debug('%s%r', method, params)
return self.server._dispatch(method, (self,) + params) return self.server._dispatch(method, (self,) + params)
class SimpleXMLRPCServer4(SimpleXMLRPCServer): class SimpleXMLRPCServer4(SimpleXMLRPCServer):
allow_reuse_address = True allow_reuse_address = True
def address_string(self):
# Workaround for http://bugs.python.org/issue6085
return self.client_address[0]
class SimpleXMLRPCServer6(SimpleXMLRPCServer4): class SimpleXMLRPCServer6(SimpleXMLRPCServer4):
...@@ -48,10 +48,10 @@ class main(object): ...@@ -48,10 +48,10 @@ class main(object):
utils.setupLog(3) utils.setupLog(3)
# Command line parsing # Command line parsing
parser = argparse.ArgumentParser( parser = utils.ArgParser(fromfile_prefix_chars='@',
description='Peer discovery http server for re6stnet') description='Peer discovery http server for re6stnet')
_ = parser.add_argument _ = parser.add_argument
_('--port', required=True, type=int, help='Port of the host server') _('--port', type=int, default=80, help='Port of the host server')
_('--db', required=True, _('--db', required=True,
help='Path to database file') help='Path to database file')
_('--ca', required=True, _('--ca', required=True,
...@@ -76,21 +76,21 @@ class main(object): ...@@ -76,21 +76,21 @@ class main(object):
address text not null, address text not null,
date integer default (strftime('%s','now')))""") date integer default (strftime('%s','now')))""")
self.db.execute("CREATE INDEX IF NOT EXISTS peers_ping ON peers(date)") self.db.execute("CREATE INDEX IF NOT EXISTS peers_ping ON peers(date)")
self.db.execute("""CREATE TABLE IF NOT EXISTS tokens ( self.db.execute("""CREATE TABLE IF NOT EXISTS token (
token text primary key not null, token text primary key not null,
email text not null, email text not null,
prefix_len integer not null, prefix_len integer not null,
date integer not null)""") date integer not null)""")
try: try:
self.db.execute("""CREATE TABLE vpn ( self.db.execute("""CREATE TABLE cert (
prefix text primary key not null, prefix text primary key not null,
email text, email text,
cert text)""") cert text)""")
except sqlite3.OperationalError, e: except sqlite3.OperationalError, e:
if e.args[0] != 'table vpn already exists': if e.args[0] != 'table cert already exists':
raise RuntimeError raise RuntimeError
else: else:
self.db.execute("INSERT INTO vpn VALUES ('',null,null)") self.db.execute("INSERT INTO cert VALUES ('',null,null)")
# Loading certificates # Loading certificates
with open(self.config.ca) as f: with open(self.config.ca) as f:
...@@ -124,7 +124,7 @@ class main(object): ...@@ -124,7 +124,7 @@ class main(object):
token = ''.join(random.sample(string.ascii_lowercase, 8)) token = ''.join(random.sample(string.ascii_lowercase, 8))
# Updating database # Updating database
try: try:
self.db.execute("INSERT INTO tokens VALUES (?,?,?,?)", (token, email, 16, int(time.time()))) self.db.execute("INSERT INTO token VALUES (?,?,?,?)", (token, email, 16, int(time.time())))
break break
except sqlite3.IntegrityError: except sqlite3.IntegrityError:
pass pass
...@@ -143,18 +143,18 @@ class main(object): ...@@ -143,18 +143,18 @@ class main(object):
max_len = 128 - len(self.network) max_len = 128 - len(self.network)
assert 0 < prefix_len <= max_len assert 0 < prefix_len <= max_len
try: try:
prefix, = self.db.execute("""SELECT prefix FROM vpn WHERE length(prefix) <= ? AND cert is null prefix, = self.db.execute("""SELECT prefix FROM cert WHERE length(prefix) <= ? AND cert is null
ORDER BY length(prefix) DESC""", (prefix_len,)).next() ORDER BY length(prefix) DESC""", (prefix_len,)).next()
except StopIteration: except StopIteration:
logging.error('There are no more free /%s prefix available' % (prefix_len,)) logging.error('There are no more free /%s prefix available' % (prefix_len,))
raise raise
while len(prefix) < prefix_len: while len(prefix) < prefix_len:
self.db.execute("UPDATE vpn SET prefix = ? WHERE prefix = ?", (prefix + '1', prefix)) self.db.execute("UPDATE cert SET prefix = ? WHERE prefix = ?", (prefix + '1', prefix))
prefix += '0' prefix += '0'
self.db.execute("INSERT INTO vpn VALUES (?,null,null)", (prefix,)) self.db.execute("INSERT INTO cert VALUES (?,null,null)", (prefix,))
if len(prefix) < max_len or '1' in prefix: if len(prefix) < max_len or '1' in prefix:
return prefix return prefix
self.db.execute("UPDATE vpn SET cert = 'reserved' WHERE prefix = ?", (prefix,)) self.db.execute("UPDATE cert SET cert = 'reserved' WHERE prefix = ?", (prefix,))
return self._getPrefix(prefix_len) return self._getPrefix(prefix_len)
def requestCertificate(self, handler, token, cert_req): def requestCertificate(self, handler, token, cert_req):
...@@ -162,11 +162,11 @@ class main(object): ...@@ -162,11 +162,11 @@ class main(object):
req = crypto.load_certificate_request(crypto.FILETYPE_PEM, cert_req) req = crypto.load_certificate_request(crypto.FILETYPE_PEM, cert_req)
with self.db: with self.db:
try: try:
token, email, prefix_len, _ = self.db.execute("SELECT * FROM tokens WHERE token = ?", (token,)).next() token, email, prefix_len, _ = self.db.execute("SELECT * FROM token WHERE token = ?", (token,)).next()
except StopIteration: except StopIteration:
logging.exception('Bad token (%s) in request' % (token,)) logging.exception('Bad token (%s) in request' % (token,))
raise raise
self.db.execute("DELETE FROM tokens WHERE token = ?", (token,)) self.db.execute("DELETE FROM token WHERE token = ?", (token,))
# Get a new prefix # Get a new prefix
prefix = self._getPrefix(prefix_len) prefix = self._getPrefix(prefix_len)
...@@ -185,7 +185,7 @@ class main(object): ...@@ -185,7 +185,7 @@ class main(object):
cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert) cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
# Insert certificate into db # Insert certificate into db
self.db.execute("UPDATE vpn SET email = ?, cert = ? WHERE prefix = ?", (email, cert, prefix)) self.db.execute("UPDATE cert SET email = ?, cert = ? WHERE prefix = ?", (email, cert, prefix))
return cert return cert
except: except:
...@@ -199,16 +199,14 @@ class main(object): ...@@ -199,16 +199,14 @@ class main(object):
return 'http://[%s]:%u' % (self.config.private, self.config.port) return 'http://[%s]:%u' % (self.config.private, self.config.port)
def getBootstrapPeer(self, handler, client_prefix): def getBootstrapPeer(self, handler, client_prefix):
cert, = self.db.execute("SELECT cert FROM vpn WHERE prefix = ?", cert, = self.db.execute("SELECT cert FROM cert WHERE prefix = ?",
(client_prefix,)).next() (client_prefix,)).next()
logging.trace('Getting bootpeer info...')
try: try:
prefix, address = self.db.execute("""SELECT prefix, address FROM peers prefix, address = self.db.execute("""SELECT prefix, address FROM peers
WHERE prefix != ? ORDER BY random() LIMIT 1""", (client_prefix,)).next() WHERE prefix != ? ORDER BY random() LIMIT 1""", (client_prefix,)).next()
except StopIteration: except StopIteration:
logging.info('No peer to send for bootstrap') logging.info('No peer to send for bootstrap')
raise raise
logging.trace('Gotten bootpeer info from db')
r, w = os.pipe() r, w = os.pipe()
try: try:
threading.Thread(target=os.write, args=(w, cert)).start() threading.Thread(target=os.write, args=(w, cert)).start()
...@@ -221,12 +219,11 @@ class main(object): ...@@ -221,12 +219,11 @@ class main(object):
os.close(w) os.close(w)
def declare(self, handler, address): def declare(self, handler, address):
print "declaring new node"
client_address, _, _, _ = handler.client_address client_address, _, _, _ = handler.client_address
client_ip = utils.binFromIp(client_address) client_ip = utils.binFromIp(client_address)
if client_ip.startswith(self.network): if client_ip.startswith(self.network):
prefix = client_ip[len(self.network):] prefix = client_ip[len(self.network):]
prefix, = self.db.execute("SELECT prefix FROM vpn WHERE prefix <= ? ORDER BY prefix DESC LIMIT 1", (prefix,)).next() prefix, = self.db.execute("SELECT prefix FROM cert WHERE prefix <= ? ORDER BY prefix DESC LIMIT 1", (prefix,)).next()
self.db.execute("INSERT OR REPLACE INTO peers (prefix, address) VALUES (?,?)", (prefix, address)) self.db.execute("INSERT OR REPLACE INTO peers (prefix, address) VALUES (?,?)", (prefix, address))
return True return True
else: else:
......
...@@ -10,7 +10,7 @@ class PeerManager: ...@@ -10,7 +10,7 @@ class PeerManager:
# internal ip = temp arg/attribute # internal ip = temp arg/attribute
def __init__(self, db_path, registry, key_path, refresh_time, address, def __init__(self, db_path, registry, key_path, refresh_time, address,
internal_ip, prefix, manual, pp, db_size): internal_ip, prefix, ip_changed, db_size):
self._refresh_time = refresh_time self._refresh_time = refresh_time
self.address = address self.address = address
self._internal_ip = internal_ip self._internal_ip = internal_ip
...@@ -18,8 +18,7 @@ class PeerManager: ...@@ -18,8 +18,7 @@ class PeerManager:
self.db_size = db_size self.db_size = db_size
self._registry = registry self._registry = registry
self._key_path = key_path self._key_path = key_path
self._pp = pp self._ip_changed = ip_changed
self._manual = manual
self.tunnel_manager = None self.tunnel_manager = None
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
...@@ -53,7 +52,15 @@ class PeerManager: ...@@ -53,7 +52,15 @@ class PeerManager:
a, = self._db.execute("SELECT value FROM config WHERE name='registry'").next() a, = self._db.execute("SELECT value FROM config WHERE name='registry'").next()
except StopIteration: except StopIteration:
proxy = xmlrpclib.ServerProxy(registry) proxy = xmlrpclib.ServerProxy(registry)
a = proxy.getPrivateAddress() retry = 1
while True:
try:
a = proxy.getPrivateAddress()
break
except socket.error, e:
logging.warning(e)
time.sleep(retry)
retry = min(60, retry * 2)
self._db.execute("INSERT INTO config VALUES ('registry',?)", (a,)) self._db.execute("INSERT INTO config VALUES ('registry',?)", (a,))
self._proxy = xmlrpclib.ServerProxy(a) self._proxy = xmlrpclib.ServerProxy(a)
logging.debug('Database prepared') logging.debug('Database prepared')
...@@ -97,7 +104,7 @@ class PeerManager: ...@@ -97,7 +104,7 @@ class PeerManager:
self.next_refresh = time.time() + self._refresh_time self.next_refresh = time.time() + self._refresh_time
logging.debug('Info sent') logging.debug('Info sent')
else: else:
logging.warning("Warning : couldn't send ip, unknown external config. retrying in 30s") logging.warning("Could not send ip, unknown external config. retrying in 30s")
def getUnusedPeers(self, peer_count): def getUnusedPeers(self, peer_count):
for populate in self._bootstrap, bool: for populate in self._bootstrap, bool:
...@@ -107,7 +114,7 @@ class PeerManager: ...@@ -107,7 +114,7 @@ class PeerManager:
if peer_list: if peer_list:
return peer_list return peer_list
populate() populate()
logging.warning('Cannot find any new peers') logging.warning('Can not find any new peer')
return [] return []
def _bootstrap(self): def _bootstrap(self):
...@@ -152,22 +159,19 @@ class PeerManager: ...@@ -152,22 +159,19 @@ class PeerManager:
self.whitelist(utils.binFromSubnet(arg)) self.whitelist(utils.binFromSubnet(arg))
logging.info('%s has disconnected' % (arg,)) logging.info('%s has disconnected' % (arg,))
elif script_type == 'route-up': elif script_type == 'route-up':
if not self._manual: if self._ip_changed:
external_ip = arg address = self._ip_changed(arg)
new_address = list([external_ip, port, proto] if self.address != address:
for port, proto, _ in self._pp) self.address = address
if self.address != new_address: logging.info('Received new external ip : %s', arg)
self.address = new_address
logging.info('Received new external ip : %s'
% (external_ip,))
try: try:
self._declare() self._declare()
except socket.error, e: except socket.error, e:
logging.debug('socket.error : %s' % e) logging.info("Connection to server failed while"
logging.info("""Connection to server failed while declaring external infos""") " declaring external infos (%s)", e)
else: else:
logging.debug('Unknow message recieved from the openvpn pipe : %s' logging.info("Unknown message received from the openvpn pipe: %s",
% msg) msg)
def readSocket(self): def readSocket(self):
msg = self.socket_file.readline() msg = self.socket_file.readline()
......
import errno
import os import os
import subprocess import subprocess
import logging import logging
import utils import utils
verbose = 0
here = os.path.realpath(os.path.dirname(__file__)) here = os.path.realpath(os.path.dirname(__file__))
ovpn_server = os.path.join(here, 'ovpn-server') ovpn_server = os.path.join(here, 'ovpn-server')
ovpn_client = os.path.join(here, 'ovpn-client') ovpn_client = os.path.join(here, 'ovpn-client')
def openvpn(hello_interval, encrypt, *args, **kw): def openvpn(iface, hello_interval, encrypt, *args, **kw):
args = ['openvpn', args = ['openvpn',
'--dev-type', 'tap', '--dev-type', 'tap',
'--dev', iface,
'--persist-tun', '--persist-tun',
'--persist-key', '--persist-key',
'--script-security', '2', '--script-security', '2',
'--user', 'nobody',
'--ping-exit', str(4 * hello_interval), '--ping-exit', str(4 * hello_interval),
'--group', 'nogroup', #'--user', 'nobody', '--group', 'nogroup',
] + list(args) ] + list(args)
if not encrypt: if not encrypt:
args.extend(['--cipher', 'none']) args.extend(['--cipher', 'none'])
logging.trace('%s' % (args,)) logging.trace('%r', args)
return subprocess.Popen(args, **kw) fd = os.open(os.path.join(log, '%s.log' % iface),
os.O_WRONLY | os.O_CREAT | os.O_APPEND)
try:
return subprocess.Popen(args, stdout=fd, stderr=subprocess.STDOUT, **kw)
finally:
os.close(fd)
def server(server_ip, ip_length, max_clients, dh_path, pipe_fd, port, proto, hello_interval, encrypt, *args, **kw): def server(iface, server_ip, ip_length, max_clients, dh_path, pipe_fd, port, proto, hello_interval, encrypt, *args, **kw):
logging.debug('Starting server...') logging.debug('Starting server...')
if server_ip != '': if server_ip:
script_up = '%s %s/%u' % (ovpn_server, server_ip, 64) script_up = '%s %s/%u' % (ovpn_server, server_ip, 64)
else: else:
script_up = '%s none' % (ovpn_server) script_up = '%s none' % (ovpn_server)
return openvpn(hello_interval, encrypt, return openvpn(iface, hello_interval, encrypt,
'--tls-server', '--tls-server',
'--mode', 'server', '--mode', 'server',
'--up', script_up, '--up', script_up,
...@@ -40,11 +45,11 @@ def server(server_ip, ip_length, max_clients, dh_path, pipe_fd, port, proto, hel ...@@ -40,11 +45,11 @@ def server(server_ip, ip_length, max_clients, dh_path, pipe_fd, port, proto, hel
'--dh', dh_path, '--dh', dh_path,
'--max-clients', str(max_clients), '--max-clients', str(max_clients),
'--port', str(port), '--port', str(port),
'--proto', proto, '--proto', 'tcp-server' if proto == 'tcp' else proto,
*args, **kw) *args, **kw)
def client(server_address, pipe_fd, hello_interval, encrypt, *args, **kw): def client(iface, server_address, pipe_fd, hello_interval, encrypt, *args, **kw):
logging.debug('Starting client...') logging.debug('Starting client...')
remote = ['--nobind', remote = ['--nobind',
'--client', '--client',
...@@ -52,18 +57,17 @@ def client(server_address, pipe_fd, hello_interval, encrypt, *args, **kw): ...@@ -52,18 +57,17 @@ def client(server_address, pipe_fd, hello_interval, encrypt, *args, **kw):
'--route-up', ovpn_client + ' ' + str(pipe_fd)] '--route-up', ovpn_client + ' ' + str(pipe_fd)]
try: try:
for ip, port, proto in utils.address_list(server_address): for ip, port, proto in utils.address_list(server_address):
if proto == 'tcp-server': remote += '--remote', ip, port, \
proto = 'tcp-client' 'tcp-client' if proto == 'tcp' else proto
remote += '--remote', ip, port, proto
except ValueError, e: except ValueError, e:
logging.warning('Error "%s" in unpacking address %s for openvpn client' logging.warning('Error "%s" in unpacking address %s for openvpn client'
% (e, server_address,)) % (e, server_address,))
remote += args remote += args
return openvpn(hello_interval, encrypt, *remote, **kw) return openvpn(iface, hello_interval, encrypt, *remote, **kw)
def router(network, subnet, subnet_size, interface_list, def router(network, subnet, subnet_size, interface_list,
wireless, hello_interval, state_path, **kw): wireless, hello_interval, verbose, pidfile, state_path, **kw):
logging.info('Starting babel...') logging.info('Starting babel...')
args = ['babeld', args = ['babeld',
'-C', 'redistribute local ip %s/%s le %s' % (subnet, subnet_size, subnet_size), '-C', 'redistribute local ip %s/%s le %s' % (subnet, subnet_size, subnet_size),
...@@ -86,8 +90,18 @@ def router(network, subnet, subnet_size, interface_list, ...@@ -86,8 +90,18 @@ def router(network, subnet, subnet_size, interface_list,
'-S', state_path, '-S', state_path,
'-s', '-s',
] ]
if pidfile:
args += '-I', pidfile
# WKRD: babeld fails to start if pidfile already exists
else:
pidfile = '/var/run/babeld.pid'
try:
os.remove(pidfile)
except OSError, e:
if e.errno != errno.ENOENT:
raise
if wireless: if wireless:
args.append('-w') args.append('-w')
args = args + interface_list args = args + interface_list
logging.trace('%s' % args) logging.trace('%r', args)
return subprocess.Popen(args, **kw) return subprocess.Popen(args, **kw)
...@@ -6,19 +6,13 @@ import utils ...@@ -6,19 +6,13 @@ import utils
# Be carfull the refresh interval should let the routes be established # Be carfull the refresh interval should let the routes be established
log = None
class Connection: class Connection:
def __init__(self, address, write_pipe, hello, iface, prefix, encrypt, def __init__(self, address, write_pipe, hello, iface, prefix, encrypt,
ovpn_args): ovpn_args):
self.process = plib.client(address, write_pipe, hello, encrypt, '--dev', iface, self.process = plib.client(iface, address, write_pipe, hello, encrypt,
*ovpn_args, stdout=os.open(os.path.join(log, *ovpn_args)
're6stnet.client.%s.log' % (prefix,)),
os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
stderr=subprocess.STDOUT)
self.iface = iface self.iface = iface
self.routes = 0 self.routes = 0
self._prefix = prefix self._prefix = prefix
...@@ -61,10 +55,9 @@ class TunnelManager: ...@@ -61,10 +55,9 @@ class TunnelManager:
for i in xrange(1, self._client_count + 1)) for i in xrange(1, self._client_count + 1))
def refresh(self): def refresh(self):
logging.info('Checking the tunnels...') logging.debug('Checking tunnels...')
self._cleanDeads() self._cleanDeads()
if self._next_tunnel_refresh < time.time(): if self._next_tunnel_refresh < time.time():
logging.info('Refreshing the tunnels...')
self._countRoutes() self._countRoutes()
self._removeSomeTunnels() self._removeSomeTunnels()
self._next_tunnel_refresh = time.time() + self._refresh_time self._next_tunnel_refresh = time.time() + self._refresh_time
...@@ -84,12 +77,12 @@ class TunnelManager: ...@@ -84,12 +77,12 @@ class TunnelManager:
self._client_count + self._refresh_count)]: self._client_count + self._refresh_count)]:
self._kill(prefix) self._kill(prefix)
def _kill(self, prefix): def _kill(self, prefix, kill=False):
logging.info('Killing the connection with %s/%u...' logging.info('Killing the connection with %s/%u...'
% (hex(int(prefix, 2))[2:], len(prefix))) % (hex(int(prefix, 2))[2:], len(prefix)))
connection = self._connection_dict.pop(prefix) connection = self._connection_dict.pop(prefix)
try: try:
connection.process.terminate() getattr(connection.process, 'kill' if kill else 'terminate')()
except OSError: except OSError:
# If the process is already exited # If the process is already exited
pass pass
...@@ -144,7 +137,7 @@ class TunnelManager: ...@@ -144,7 +137,7 @@ class TunnelManager:
if iface in self._iface_list and self._net_len < subnet_size < 128: if iface in self._iface_list and self._net_len < subnet_size < 128:
prefix = ip[self._net_len:subnet_size] prefix = ip[self._net_len:subnet_size]
logging.debug('A route to %s has been discovered on the LAN' logging.debug('A route to %s has been discovered on the LAN'
% (hex(int(prefix), 2)[2:])) % hex(int(prefix, 2))[2:])
self._peer_db.blacklist(prefix, 0) self._peer_db.blacklist(prefix, 0)
possiblePeers.add(line[0]) possiblePeers.add(line[0])
...@@ -163,7 +156,7 @@ class TunnelManager: ...@@ -163,7 +156,7 @@ class TunnelManager:
def killAll(self): def killAll(self):
for prefix in self._connection_dict.keys(): for prefix in self._connection_dict.keys():
self._kill(prefix) self._kill(prefix, True)
def checkIncomingTunnel(self, prefix): def checkIncomingTunnel(self, prefix):
if prefix in self._connection_dict: if prefix in self._connection_dict:
......
...@@ -3,57 +3,48 @@ import logging ...@@ -3,57 +3,48 @@ import logging
import time import time
class NoUPnPDevice(Exception):
def __init__(self):
pass
def __str__(self):
return 'No upnp device found'
class Forwarder: class Forwarder:
def __init__(self): def __init__(self):
self._u = miniupnpc.UPnP() self._u = miniupnpc.UPnP()
self._u.discoverdelay = 200 self._u.discoverdelay = 200
self._rules = [] self._rules = []
self._u.discover() self._u.discover()
try: self._u.selectigd()
self._u.selectigd()
except:
raise NoUPnPDevice
self._external_ip = self._u.externalipaddress() self._external_ip = self._u.externalipaddress()
self.next_refresh = time.time() self.next_refresh = time.time()
def AddRule(self, local_port, proto): def addRule(self, local_port, proto):
# Init parameters # Init parameters
external_port = 1000 external_port = 1023
if proto == 'udp': desc = 're6stnet openvpn %s server' % proto
upnp_proto = 'UDP' proto = proto.upper()
elif proto == 'tcp-server': lanaddr = self._u.lanaddr
upnp_proto = 'TCP'
else:
logging.info('Unknown protocol : %s' % proto)
raise RuntimeError
# Choose a free port # Choose a free port
while True: while True:
while self._u.getspecificportmapping(external_port, external_port += 1
upnp_proto) != None: if external_port > 65535:
external_port += 1 raise Exception('Failed to redirect %u/%s via UPnP'
if external_port == 65536: % (local_port, proto))
return None try:
if not self._u.getspecificportmapping(external_port, proto):
# Make the redirection args = external_port, proto, lanaddr, local_port, desc, ''
if self._u.addportmapping(external_port, upnp_proto, self._u.lanaddr, self._u.addportmapping(*args)
int(local_port), 're6stnet openvpn server', ''): break
logging.debug('Forwarding %s:%s to %s:%s' % (self._external_ip, except Exception, e:
external_port, self._u.lanaddr, local_port)) if str(e) != 'ConflictInMappingEntry':
self._rules.append((external_port, int(local_port), upnp_proto)) raise
return (self._external_ip, str(external_port), proto) logging.debug('Forwarding %s:%s to %s:%s', self._external_ip,
external_port, self._u.lanaddr, local_port)
self._rules.append(args)
return self._external_ip, external_port
def refresh(self): def refresh(self):
logging.debug('Refreshing port forwarding') logging.debug('Refreshing port forwarding')
for external_port, local_port, proto in self._rules: for args in self._rules:
self._u.addportmapping(external_port, proto, self._u.lanaddr, self._u.addportmapping(*args)
local_port, 're6stnet openvpn server', '')
self.next_refresh = time.time() + 500 self.next_refresh = time.time() + 500
def clear(self):
for args in self._rules:
self._u.deleteportmapping(args[0], args[1])
del self.rules[:]
...@@ -6,11 +6,22 @@ logging_levels = logging.WARNING, logging.INFO, logging.DEBUG, 5 ...@@ -6,11 +6,22 @@ logging_levels = logging.WARNING, logging.INFO, logging.DEBUG, 5
def setupLog(log_level): def setupLog(log_level):
logging.basicConfig(level=logging_levels[log_level], logging.basicConfig(level=logging_levels[log_level],
format='%(asctime)s : %(message)s', format='%(asctime)s %(levelname)-9s %(message)s',
datefmt='%d-%m-%Y %H:%M:%S') datefmt='%d-%m-%Y %H:%M:%S')
logging.addLevelName(5, 'TRACE') logging.addLevelName(5, 'TRACE')
logging.trace = lambda *args, **kw: logging.log(5, *args, **kw) logging.trace = lambda *args, **kw: logging.log(5, *args, **kw)
class ArgParser(argparse.ArgumentParser):
def convert_arg_line_to_args(self, arg_line):
arg_line = arg_line.split('#')[0].rstrip()
if arg_line:
if arg_line.startswith('@'):
yield arg_line
return
for arg in ('--' + arg_line.lstrip('--')).split():
if arg.strip():
yield arg
def binFromIp(ip): def binFromIp(ip):
ip1, ip2 = struct.unpack('>QQ', socket.inet_pton(socket.AF_INET6, ip)) ip1, ip2 = struct.unpack('>QQ', socket.inet_pton(socket.AF_INET6, ip))
......
#!/usr/bin/env python #!/usr/bin/env python
import os, sys, select, time, socket import atexit, os, sys, select, socket, time
import argparse, subprocess, sqlite3, logging, traceback import argparse, signal, subprocess, sqlite3, logging, traceback
from argparse import ArgumentParser from re6st import plib, utils, db, tunnel
from re6st import plib, utils, db, upnpigd, tunnel
class ArgParser(ArgumentParser):
def convert_arg_line_to_args(self, arg_line):
arg_line = arg_line.split('#')[0].rstrip()
if arg_line:
if arg_line.startswith('@'):
yield arg_line
return
for arg in ('--' + arg_line.lstrip('--')).split():
if arg.strip():
yield arg
def ovpnArgs(optional_args, ca_path, cert_path, key_path): def ovpnArgs(optional_args, ca_path, cert_path, key_path):
# Treat openvpn arguments # Treat openvpn arguments
...@@ -32,13 +17,13 @@ def ovpnArgs(optional_args, ca_path, cert_path, key_path): ...@@ -32,13 +17,13 @@ def ovpnArgs(optional_args, ca_path, cert_path, key_path):
def getConfig(): def getConfig():
parser = ArgParser(fromfile_prefix_chars='@', parser = utils.ArgParser(fromfile_prefix_chars='@',
description='Resilient virtual private network application') description='Resilient virtual private network application')
_ = parser.add_argument _ = parser.add_argument
# General Configuration options # General Configuration options
_('--ip', default=None, dest='address', action='append', nargs=3, _('--ip',
help='Ip address, port and protocol advertised to other vpn nodes') help='IP address advertised to other nodes')
_('--registry', required=True, _('--registry', required=True,
help="HTTP URL of the discovery peer server," help="HTTP URL of the discovery peer server,"
" with public host (default port: 80)") " with public host (default port: 80)")
...@@ -49,11 +34,15 @@ def getConfig(): ...@@ -49,11 +34,15 @@ def getConfig():
_('-s', '--state', default='/var/lib/re6stnet', _('-s', '--state', default='/var/lib/re6stnet',
help='Path to re6stnet state directory') help='Path to re6stnet state directory')
_('-v', '--verbose', default=0, type=int, _('-v', '--verbose', default=0, type=int,
help='Defines the verbose level') help='Log level of re6st itself')
_('-i', '--interface', action='append', dest='iface_list', default=[], _('-i', '--interface', action='append', dest='iface_list', default=[],
help='Extra interface for LAN discovery') help='Extra interface for LAN discovery')
# Routing algorithm options # Routing algorithm options
_('--babel-pidfile',
help='Specify a file to write our process id to')
_('--babel-verb', default=0,
help='Babel verbosity')
_('--hello', type=int, default=15, _('--hello', type=int, default=15,
help='Hello interval for babel, in seconds') help='Hello interval for babel, in seconds')
_('-w', '--wireless', action='store_true', _('-w', '--wireless', action='store_true',
...@@ -87,11 +76,6 @@ def getConfig(): ...@@ -87,11 +76,6 @@ def getConfig():
def main(): def main():
# Get arguments # Get arguments
config = getConfig() config = getConfig()
if not config.pp:
config.pp = [['1194', 'udp'], ['1194', 'tcp-server']]
config.pp = list((port, proto, 're6stnet-%s' % proto)
for port, proto in config.pp)
manual = bool(config.address)
network = utils.networkFromCa(config.ca) network = utils.networkFromCa(config.ca)
internal_ip, prefix = utils.ipFromCert(network, config.cert) internal_ip, prefix = utils.ipFromCert(network, config.cert)
openvpn_args = ovpnArgs(config.openvpn_args, config.ca, config.cert, openvpn_args = ovpnArgs(config.openvpn_args, config.ca, config.cert,
...@@ -104,76 +88,76 @@ def main(): ...@@ -104,76 +88,76 @@ def main():
logging.trace("Configuration :\n%s" % config) logging.trace("Configuration :\n%s" % config)
# Set global variables # Set global variables
tunnel.log = config.log plib.log = tunnel.log = config.log
plib.verbose = config.verbose
# Create and open read_only pipe to get server events # Create and open read_only pipe to get server events
logging.info('Creating pipe for server events...') logging.info('Creating pipe for server events...')
r_pipe, write_pipe = os.pipe() r_pipe, write_pipe = os.pipe()
read_pipe = os.fdopen(r_pipe) read_pipe = os.fdopen(r_pipe)
logging.debug('Pipe created')
signal.signal(signal.SIGHUP, lambda *args: sys.exit(-1))
# Init db and tunnels # Init db and tunnels
forwarder = None address = []
if manual: if config.pp:
logging.info('Detected manual external configuration') pp = [(int(port), proto) for port, proto in config.pp]
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)):
logging.warning("""Beware: in manual configuration, you
declared less external configurations regarding
protocol %s/%s than you gave internal server
configurations""" % (c, s))
else: else:
pp = (1194, 'udp'), (1194, 'tcp')
ip_changed = lambda ip: [(ip, str(port), proto) for port, proto in pp]
forwarder = None
if config.ip == 'upnp' or not config.ip:
logging.info('Attempting automatic configuration via UPnP...') logging.info('Attempting automatic configuration via UPnP...')
try: try:
forwarder = upnpigd.Forwarder() from re6st.upnpigd import Forwarder
config.address = [] forwarder = Forwarder()
for port, proto, _ in config.pp: except Exception, e:
ext = forwarder.AddRule(port, proto) if config.ip:
if ext: raise
config.address.append(ext) logging.info("%s: assume we are not NATed", e)
except upnpigd.NoUPnPDevice: else:
logging.info('No upnp device found') atexit.register(forwarder.clear)
for port, proto in pp:
ip, port = forwarder.addRule(port, proto)
address.append((ip, str(port), proto))
elif config.ip != 'any':
address = ip_changed(config.ip)
if address:
ip_changed = None
peer_db = db.PeerManager(db_path, config.registry, config.key, peer_db = db.PeerManager(db_path, config.registry, config.key,
config.peers_db_refresh, config.address, internal_ip, prefix, config.peers_db_refresh, address, internal_ip, prefix,
manual, config.pp, 200) ip_changed, 200)
tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args, tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args,
config.hello, config.tunnel_refresh, config.connection_count, config.hello, config.tunnel_refresh, config.connection_count,
config.iface_list, network, prefix, 2, config.encrypt) config.iface_list, network, prefix, 2, config.encrypt)
peer_db.tunnel_manager = tunnel_manager peer_db.tunnel_manager = tunnel_manager
# Launch routing protocol. WARNING : you have to be root to start babeld # Launch routing protocol. WARNING : you have to be root to start babeld
server_tunnels = {}
for x in pp:
server_tunnels.setdefault('re6stnet-' + x[1], x)
interface_list = list(tunnel_manager.free_interface_set) \ interface_list = list(tunnel_manager.free_interface_set) \
+ config.iface_list + list(iface + config.iface_list + server_tunnels.keys()
for _, _, iface in config.pp)
subnet = utils.ipFromBin((network + prefix).ljust(128, '0')) subnet = utils.ipFromBin((network + prefix).ljust(128, '0'))
router = plib.router(network, subnet, len(prefix) + len(network), interface_list, router = plib.router(network, subnet, len(prefix) + len(network),
config.wireless, config.hello, os.path.join(config.state, 'babeld.state'), interface_list, config.wireless, config.hello, config.babel_verb,
stdout=os.open(os.path.join(config.log, 'babeld.log'), config.babel_pidfile, os.path.join(config.state, 'babeld.state'),
os.O_WRONLY | os.O_CREAT | os.O_TRUNC), stderr=subprocess.STDOUT) stdout=os.open(os.path.join(config.log, 'babeld.log'),
os.O_WRONLY | os.O_CREAT | os.O_APPEND), stderr=subprocess.STDOUT)
# Establish connections
server_process = []
server_ip = internal_ip
for port, proto, iface in config.pp:
server_process.append(plib.server(server_ip, len(network) + len(prefix),
config.connection_count, config.dh, write_pipe, port,
proto, config.hello, config.encrypt, '--dev', iface, *openvpn_args,
stdout=os.open(os.path.join(config.log,
're6stnet.server.%s.log' % (proto,)),
os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
stderr=subprocess.STDOUT))
server_ip = ''
# main loop # main loop
try: try:
try: try:
server_process = []
for iface, (port, proto) in server_tunnels.iteritems():
server_process.append(plib.server(iface,
internal_ip if proto == pp[0][1] else None,
len(network) + len(prefix),
config.connection_count, config.dh, write_pipe, port,
proto, config.hello, config.encrypt, *openvpn_args))
while True: while True:
logging.info('Sleeping ...')
nextUpdate = min(tunnel_manager.next_refresh, peer_db.next_refresh) nextUpdate = min(tunnel_manager.next_refresh, peer_db.next_refresh)
if forwarder != None: if forwarder:
nextUpdate = min(nextUpdate, forwarder.next_refresh) nextUpdate = min(nextUpdate, forwarder.next_refresh)
nextUpdate = max(0, nextUpdate - time.time()) nextUpdate = max(0, nextUpdate - time.time())
select_list = [read_pipe] select_list = [read_pipe]
...@@ -186,14 +170,15 @@ def main(): ...@@ -186,14 +170,15 @@ def main():
peer_db.refresh() peer_db.refresh()
if time.time() >= tunnel_manager.next_refresh: if time.time() >= tunnel_manager.next_refresh:
tunnel_manager.refresh() tunnel_manager.refresh()
if forwarder != None and time.time() > forwarder.next_refresh: if forwarder and time.time() > forwarder.next_refresh:
forwarder.refresh() forwarder.refresh()
if peer_db.socket_file in ready: if peer_db.socket_file in ready:
peer_db.readSocket() peer_db.readSocket()
finally: finally:
for p in [router] + server_process: router.terminate()
for p in server_process:
try: try:
p.terminate() p.kill()
except: except:
pass pass
try: try:
...@@ -203,7 +188,10 @@ def main(): ...@@ -203,7 +188,10 @@ def main():
except sqlite3.Error: except sqlite3.Error:
traceback.print_exc() traceback.print_exc()
os.rename(db_path, db_path + '.bak') os.rename(db_path, db_path + '.bak')
os.execvp(sys.executable, sys.argv) try:
sys.exitfunc()
finally:
os.execvp(sys.executable, sys.argv)
except KeyboardInterrupt: except KeyboardInterrupt:
return 0 return 0
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment