Commit f3ccd7a2 authored by Jondy Zhao's avatar Jondy Zhao

Merge branch 'master' into cygwin

Conflicts:
	re6stnet
parents b04b2b95 6e443638
...@@ -23,3 +23,8 @@ ...@@ -23,3 +23,8 @@
use --main-interface option on it. use --main-interface option on it.
- Is it useful that each node regenerates its own DH parameter ? - Is it useful that each node regenerates its own DH parameter ?
- Filter non-routable IPs. Add an option not to do it.
- Abort in case of import child process failure (babel, openvpn server,
openvpn client if run with --client).
\ No newline at end of file
-----BEGIN CERTIFICATE-----
MIIDTTCCAjWgAwIBAgIHASABDbgAQjANBgkqhkiG9w0BAQUFADA+MRowGAYDVQQD
DBFyZTZzdC5leGFtcGxlLmNvbTEgMB4GCSqGSIb3DQEJARYRcmU2c3RAZXhhbXBs
ZS5jb20wHhcNMTIwOTA2MTI0MTM0WhcNMjAwMTAxMTI0MTM0WjA+MRowGAYDVQQD
DBFyZTZzdC5leGFtcGxlLmNvbTEgMB4GCSqGSIb3DQEJARYRcmU2c3RAZXhhbXBs
ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzKdaI1gddt8iN
5MTNMe+bKuVt0Cfq34OUh0y73NQm1HjUMDHiU/5djkBuw+Y8q8OMIwCuN8Pgyp6C
fFivLH3a42ebgxJn2kfU7078gibCEuIPWD9GXQjSgP+7MJVv3q24g4YZTsa6lKJ8
Y1OfP2i2uNR3v7/3BBIsob8pial2kbLgz96L+czDjPOVsV+VH91Mkq6kO7jfNpNo
BHVSXzkQpVupxi86wEFIrkzlAYZRmY0fRcprhovI1iDLpdsJsY/yyPgfXNJk9xhT
lPUoNxyH0EPhGv0KArJZkwlzdDj5RVYYcLTEge374gr1EIMyzex/kN5y1as2tX9l
wXTROc5pAgMBAAGjUDBOMB0GA1UdDgQWBBSgDNnHOCF5xSGbmA9SCujCLRs0nTAf
BgNVHSMEGDAWgBSgDNnHOCF5xSGbmA9SCujCLRs0nTAMBgNVHRMEBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQBZQvMkCSCrrJoS432kJUg//iB0+c1mftbYTez+wqHq
NzEPnv5EWJtYsYvZUx6huNvrv5UR9S9MkGyH1u8kw3mW5lRKTPBC9NdAgywhsDES
VTDx02EZhsKEA2VaxhirGyJEDSgXADQNZNtB0Mw+M8/tociZKOiih6gwJw3sYcDz
9mTQFG44YG2nSmxEqP2m+32km0gvxLNIyoCnZN1x25dcRcJ5H9AbbIfSZxC02rqc
Wy0HLmfa7ZPLYD5Qz/TuCXXRXxyy5AYasVsz2GdXDNXRwiEmYqfM69EDtwZqTPZj
cfJdgSNqrysIXYE6SgBi6RUtOlmBubdxke4EZZ4ImdGo
-----END CERTIFICATE-----
...@@ -5,6 +5,7 @@ IPTABLES = 'iptables' ...@@ -5,6 +5,7 @@ IPTABLES = 'iptables'
SCREEN = 'screen' SCREEN = 'screen'
VERBOSE = 4 VERBOSE = 4
REGISTRY='10.0.0.2' REGISTRY='10.0.0.2'
CA_DAYS = 1000
# registry # registry
# |.2 # |.2
...@@ -154,6 +155,10 @@ gateway1.screen('miniupnpd -d -f miniupnpd.conf -P miniupnpd.pid -a 10.1.1.1' ...@@ -154,6 +155,10 @@ gateway1.screen('miniupnpd -d -f miniupnpd.conf -P miniupnpd.pid -a 10.1.1.1'
' -i %s' % g1_if_0_name) ' -i %s' % g1_if_0_name)
if 1: if 1:
import sqlite3 import sqlite3
os.path.exists('ca.crt') or subprocess.check_call(
"openssl req -nodes -new -x509 -key registry/ca.key -out ca.crt"
" -subj /CN=re6st.example.com/emailAddress=re6st@example.com"
" -set_serial 0x120010db80042 -days %u" % CA_DAYS, shell=True)
db_path = 'registry/registry.db' db_path = 'registry/registry.db'
registry.screen('../re6st-registry @registry/re6st-registry.conf --db %s' registry.screen('../re6st-registry @registry/re6st-registry.conf --db %s'
' --mailhost %s -v%u' % (db_path, os.path.abspath('mbox'), VERBOSE)) ' --mailhost %s -v%u' % (db_path, os.path.abspath('mbox'), VERBOSE))
...@@ -189,7 +194,7 @@ if 1: ...@@ -189,7 +194,7 @@ if 1:
p.communicate(str(token[0])) p.communicate(str(token[0]))
os.remove(dh_path) os.remove(dh_path)
os.remove(folder + '/ca.crt') os.remove(folder + '/ca.crt')
node.screen('../re6stnet @%s/re6stnet.conf --table 0 -v%u --registry %s %s' node.screen('../re6stnet @%s/re6stnet.conf -v%u --registry %s %s'
% (folder, VERBOSE, registry, args)) % (folder, VERBOSE, registry, args))
re6stnet(registry, 'registry', '--ip ' + REGISTRY, registry='http://localhost/') re6stnet(registry, 'registry', '--ip ' + REGISTRY, registry='http://localhost/')
re6stnet(machine1, 'm1', '-I%s' % m1_if_0.name) re6stnet(machine1, 'm1', '-I%s' % m1_if_0.name)
...@@ -278,8 +283,9 @@ if len(sys.argv) > 1: ...@@ -278,8 +283,9 @@ if len(sys.argv) > 1:
elif self.path == '/tunnel.html': elif self.path == '/tunnel.html':
other = 'route' other = 'route'
gv = registry.Popen(('python', '-c', r"""if 1: gv = registry.Popen(('python', '-c', r"""if 1:
import math, xmlrpclib import math
g = xmlrpclib.ServerProxy('http://localhost/').topology() from re6st.registry import RegistryClient
g = eval(RegistryClient('http://localhost/').topology())
print 'digraph {' print 'digraph {'
a = 2 * math.pi / len(g) a = 2 * math.pi / len(g)
z = 4 z = 4
...@@ -293,7 +299,7 @@ if len(sys.argv) > 1: ...@@ -293,7 +299,7 @@ if len(sys.argv) > 1:
for p in p or (): for p in p or ():
print '"%s" -> "%s";' % (n, title(p)) print '"%s" -> "%s";' % (n, title(p))
print '}' print '}'
"""), stdout=subprocess.PIPE).communicate()[0] """), stdout=subprocess.PIPE, cwd="..").communicate()[0]
if gv: if gv:
svg = subprocess.Popen(('neato', '-Tsvg'), svg = subprocess.Popen(('neato', '-Tsvg'),
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
......
...@@ -7,5 +7,6 @@ dh dh2048.pem ...@@ -7,5 +7,6 @@ dh dh2048.pem
ca ca.crt ca ca.crt
cert m1/cert.crt cert m1/cert.crt
key m1/cert.key key m1/cert.key
table 0
client-count 2 client-count 2
tunnel-refresh 100 tunnel-refresh 100
...@@ -7,5 +7,6 @@ dh dh2048.pem ...@@ -7,5 +7,6 @@ dh dh2048.pem
ca ca.crt ca ca.crt
cert m2/cert.crt cert m2/cert.crt
key m2/cert.key key m2/cert.key
table 0
client-count 2 client-count 2
tunnel-refresh 100 tunnel-refresh 100
...@@ -7,5 +7,6 @@ dh dh2048.pem ...@@ -7,5 +7,6 @@ dh dh2048.pem
ca ca.crt ca ca.crt
cert m3/cert.crt cert m3/cert.crt
key m3/cert.key key m3/cert.key
table 0
client-count 2 client-count 2
tunnel-refresh 100 tunnel-refresh 100
...@@ -7,5 +7,6 @@ dh dh2048.pem ...@@ -7,5 +7,6 @@ dh dh2048.pem
ca ca.crt ca ca.crt
cert m4/cert.crt cert m4/cert.crt
key m4/cert.key key m4/cert.key
table 0
client-count 2 client-count 2
tunnel-refresh 100 tunnel-refresh 100
...@@ -5,5 +5,6 @@ hello 4 ...@@ -5,5 +5,6 @@ hello 4
ca ca.crt ca ca.crt
cert m5/cert.crt cert m5/cert.crt
key m5/cert.key key m5/cert.key
table 0
client-count 0 client-count 0
max-clients 0 max-clients 0
...@@ -7,6 +7,7 @@ dh dh2048.pem ...@@ -7,6 +7,7 @@ dh dh2048.pem
ca ca.crt ca ca.crt
cert m6/cert.crt cert m6/cert.crt
key m6/cert.key key m6/cert.key
table 0
client-count 2 client-count 2
tunnel-refresh 100 tunnel-refresh 100
# TODO: Run a DHCPv4 client on machine9. Unfortunately, isc-dhcp-client 4.2.4 # TODO: Run a DHCPv4 client on machine9. Unfortunately, isc-dhcp-client 4.2.4
......
...@@ -7,5 +7,6 @@ dh dh2048.pem ...@@ -7,5 +7,6 @@ dh dh2048.pem
ca ca.crt ca ca.crt
cert m7/cert.crt cert m7/cert.crt
key m7/cert.key key m7/cert.key
table 0
client-count 2 client-count 2
tunnel-refresh 100 tunnel-refresh 100
...@@ -5,4 +5,5 @@ hello 4 ...@@ -5,4 +5,5 @@ hello 4
ca ca.crt ca ca.crt
cert m8/cert.crt cert m8/cert.crt
key m8/cert.key key m8/cert.key
table 0
client 10.0.1.2,1194,udp;10.0.1.3,1194,udp client 10.0.1.2,1194,udp;10.0.1.3,1194,udp
...@@ -6,5 +6,6 @@ dh dh2048.pem ...@@ -6,5 +6,6 @@ dh dh2048.pem
ca ca.crt ca ca.crt
cert registry/cert.crt cert registry/cert.crt
key registry/cert.key key registry/cert.key
gateway
client-count 2 client-count 2
tunnel-refresh 100 tunnel-refresh 100
#!/usr/bin/python #!/usr/bin/python
import argparse, atexit, errno, os, subprocess, sqlite3, sys, xmlrpclib import argparse, atexit, errno, os, subprocess, sqlite3, sys, time
from OpenSSL import crypto from OpenSSL import crypto
from re6st import utils from re6st import registry, utils
def create(path, text=None, mode=0666): def create(path, text=None, mode=0666):
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, mode) fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, mode)
...@@ -10,6 +10,9 @@ def create(path, text=None, mode=0666): ...@@ -10,6 +10,9 @@ def create(path, text=None, mode=0666):
finally: finally:
os.close(fd) os.close(fd)
def loadCert(pem):
return crypto.load_certificate(crypto.FILETYPE_PEM, pem)
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Setup script for re6stnet.", description="Setup script for re6stnet.",
...@@ -47,11 +50,11 @@ def main(): ...@@ -47,11 +50,11 @@ def main():
dh_path = 'dh2048.pem' dh_path = 'dh2048.pem'
# Establish connection with server # Establish connection with server
s = xmlrpclib.ServerProxy(config.registry, allow_none=True) s = registry.RegistryClient(config.registry)
# Get CA # Get CA
ca = s.getCa() ca = s.getCa()
network = utils.networkFromCa(ca) network = utils.networkFromCa(loadCert(ca))
if config.is_needed: if config.is_needed:
route, err = subprocess.Popen(('ip', '-6', '-o', 'route', 'get', route, err = subprocess.Popen(('ip', '-6', '-o', 'route', 'get',
utils.ipFromBin(network)), utils.ipFromBin(network)),
...@@ -72,7 +75,7 @@ def main(): ...@@ -72,7 +75,7 @@ def main():
req = crypto.X509Req() req = crypto.X509Req()
try: try:
with open(cert_path) as f: with open(cert_path) as f:
cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read()) cert = loadCert(f.read())
components = dict(cert.get_subject().get_components()) components = dict(cert.get_subject().get_components())
components.pop('CN', None) components.pop('CN', None)
except IOError, e: except IOError, e:
...@@ -136,7 +139,12 @@ def main(): ...@@ -136,7 +139,12 @@ def main():
os.ftruncate(cert_fd, len(cert)) os.ftruncate(cert_fd, len(cert))
os.close(cert_fd) os.close(cert_fd)
print "Certificate setup complete." cert = loadCert(cert)
not_after = utils.notAfter(cert)
print("Setup complete. Certificate is valid until %s UTC"
" and will be automatically renewed after %s UTC" % (
time.asctime(time.gmtime(not_after)),
time.asctime(time.gmtime(not_after - registry.RENEW_PERIOD))))
if not os.path.lexists(conf_path): if not os.path.lexists(conf_path):
create(conf_path, """\ create(conf_path, """\
......
This diff is collapsed.
import logging, sqlite3, socket, subprocess, xmlrpclib, time import logging, sqlite3, socket, subprocess, time
from urllib import splittype, splithost, splitport from . import utils
import utils
class PeerDB(object): class PeerDB(object):
# internal ip = temp arg/attribute # internal ip = temp arg/attribute
def __init__(self, db_path, registry, key_path, prefix, db_size=200): def __init__(self, db_path, registry, key_path, network, prefix,
db_size=200):
self._prefix = prefix self._prefix = prefix
self._db_size = db_size self._db_size = db_size
self._key_path = key_path self._key_path = key_path
self._proxy = xmlrpclib.ServerProxy(registry) self._registry = registry
logging.info('Initialize cache ...') logging.info('Initialize cache ...')
self._db = sqlite3.connect(db_path, isolation_level=None) self._db = sqlite3.connect(db_path, isolation_level=None)
...@@ -25,27 +25,35 @@ class PeerDB(object): ...@@ -25,27 +25,35 @@ class PeerDB(object):
value text)""") value text)""")
q('ATTACH DATABASE ":memory:" AS volatile') q('ATTACH DATABASE ":memory:" AS volatile')
q("""CREATE TABLE volatile.stat ( q("""CREATE TABLE volatile.stat (
peer TEXT PRIMARY KEY REFERENCES peer(prefix) ON DELETE CASCADE, peer TEXT PRIMARY KEY,
try INTEGER NOT NULL DEFAULT 0)""") try INTEGER NOT NULL DEFAULT 0)""")
q("CREATE INDEX volatile.stat_try ON stat(try)") q("CREATE INDEX volatile.stat_try ON stat(try)")
q("INSERT INTO volatile.stat (peer) SELECT prefix FROM peer") q("INSERT INTO volatile.stat (peer) SELECT prefix FROM peer")
try: try:
a = q("SELECT value FROM config WHERE name='registry'").next()[0] a = q("SELECT value FROM config WHERE name='registry'").next()[0]
except StopIteration: except StopIteration:
logging.info("Private IP of registry not in cache." a = self._updateRegistryIP()
" Asking registry via its public IP ...") else:
self.registry_ip = utils.binFromIp(a)
if not self.registry_ip.startswith(network):
a = self._updateRegistryIP()
logging.info("Cache initialized. Registry IP is %s", a)
def _updateRegistryIP(self):
logging.info("Asking registry its private IP...")
retry = 1 retry = 1
while True: while True:
try: try:
a = self._proxy.getPrivateAddress() a = self._registry.getPrivateAddress(self._prefix)
break break
except socket.error, e: except socket.error, e:
logging.warning(e) logging.warning(e)
time.sleep(retry) time.sleep(retry)
retry = min(60, retry * 2) retry = min(60, retry * 2)
q("INSERT INTO config VALUES ('registry',?)", (a,)) self._db.execute("INSERT OR REPLACE INTO config VALUES ('registry',?)",
(a,))
self.registry_ip = utils.binFromIp(a) self.registry_ip = utils.binFromIp(a)
logging.info("Cache initialized. Registry IP is %s", a) return a
def log(self): def log(self):
if logging.getLogger().isEnabledFor(5): if logging.getLogger().isEnabledFor(5):
...@@ -83,31 +91,41 @@ class PeerDB(object): ...@@ -83,31 +91,41 @@ class PeerDB(object):
def getBootstrapPeer(self): def getBootstrapPeer(self):
logging.info('Getting Boot peer...') logging.info('Getting Boot peer...')
try: try:
bootpeer = self._proxy.getBootstrapPeer(self._prefix).data bootpeer = self._registry.getBootstrapPeer(self._prefix)
except (socket.error, xmlrpclib.Fault), e: prefix, address = utils.decrypt(self._key_path, bootpeer).split()
except (socket.error, subprocess.CalledProcessError, ValueError), e:
logging.warning('Failed to bootstrap (%s)', e) logging.warning('Failed to bootstrap (%s)', e)
else: else:
p = subprocess.Popen(('openssl', 'rsautl', '-decrypt', '-inkey', self._key_path), if prefix != self._prefix:
stdin=subprocess.PIPE, stdout=subprocess.PIPE) self.addPeer(prefix, address)
bootpeer = p.communicate(bootpeer)[0].split() return prefix, address
if bootpeer[0] != self._prefix:
self.addPeer(*bootpeer)
return bootpeer
logging.warning('Buggy registry sent us our own address') logging.warning('Buggy registry sent us our own address')
def addPeer(self, prefix, address, force=False): def addPeer(self, prefix, address, set_preferred=False):
logging.debug('Adding peer %s: %s', prefix, address) logging.debug('Adding peer %s: %s', prefix, address)
with self._db: with self._db:
q = self._db.execute q = self._db.execute
try: try:
(a,), = q("SELECT address FROM peer WHERE prefix=?", (prefix,)) (a,), = q("SELECT address FROM peer WHERE prefix=?", (prefix,))
a = a != address if force else \ if set_preferred:
set(a.split(';')) != set(address.split(';')) preferred = address.split(';')
address = a
else:
preferred = a.split(';')
def key(a):
try:
return preferred.index(a)
except ValueError:
return len(preferred)
address = ';'.join(sorted(address.split(';'), key=key))
except ValueError: except ValueError:
q("DELETE FROM peer WHERE prefix IN (SELECT peer" a = q("SELECT peer FROM volatile.stat ORDER BY try, RANDOM()"
" FROM volatile.stat ORDER BY try, RANDOM() LIMIT ?,-1)", " LIMIT ?,-1", (self._db_size,)).fetchall()
(self._db_size,))
a = True
if a: if a:
qq = self._db.executemany
qq("DELETE FROM peer WHERE prefix IN (?)", a)
qq("DELETE FROM volatile.stat WHERE peer IN (?)", a)
# 'a != address' will evaluate to True because types differs
if a != address:
q("INSERT OR REPLACE INTO peer VALUES (?,?)", (prefix, address)) q("INSERT OR REPLACE INTO peer VALUES (?,?)", (prefix, address))
q("INSERT OR REPLACE INTO volatile.stat VALUES (?,0)", (prefix,)) q("INSERT OR REPLACE INTO volatile.stat VALUES (?,0)", (prefix,))
This diff is collapsed.
...@@ -41,8 +41,8 @@ class MultiGatewayManager(dict): ...@@ -41,8 +41,8 @@ class MultiGatewayManager(dict):
class Connection(object): class Connection(object):
def __init__(self, address, iface, prefix): def __init__(self, address_list, iface, prefix):
self.address_list = list(utils.parse_address(address)) self.address_list = address_list
self.iface = iface self.iface = iface
self.routes = 0 self.routes = 0
self._prefix = prefix self._prefix = prefix
...@@ -78,8 +78,7 @@ class Connection(object): ...@@ -78,8 +78,7 @@ class Connection(object):
except TypeError: except TypeError:
i = len(self.address_list) - 1 i = len(self.address_list) - 1
if i: if i:
db.addPeer(self._prefix, utils.dump_address( db.addPeer(self._prefix, ','.join(self.address_list[i]), True)
self.address_list[i:] + self.address_list[:i]), True)
else: else:
db.connecting(self._prefix, 0) db.connecting(self._prefix, 0)
...@@ -106,7 +105,7 @@ class TunnelManager(object): ...@@ -106,7 +105,7 @@ class TunnelManager(object):
def __init__(self, write_pipe, peer_db, openvpn_args, timeout, def __init__(self, write_pipe, peer_db, openvpn_args, timeout,
refresh, client_count, iface_list, network, prefix, refresh, client_count, iface_list, network, prefix,
address, ip_changed, encrypt, remote_gateway): address, ip_changed, encrypt, remote_gateway, disable_proto):
self._write_pipe = write_pipe self._write_pipe = write_pipe
self._peer_db = peer_db self._peer_db = peer_db
self._connecting = set() self._connecting = set()
...@@ -125,6 +124,7 @@ class TunnelManager(object): ...@@ -125,6 +124,7 @@ class TunnelManager(object):
self._encrypt = encrypt self._encrypt = encrypt
self._gateway_manager = MultiGatewayManager(remote_gateway) \ self._gateway_manager = MultiGatewayManager(remote_gateway) \
if remote_gateway else None if remote_gateway else None
self._disable_proto = disable_proto
self._served = set() self._served = set()
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
...@@ -216,6 +216,11 @@ class TunnelManager(object): ...@@ -216,6 +216,11 @@ class TunnelManager(object):
if prefix in self._served or prefix in self._connection_dict: if prefix in self._served or prefix in self._connection_dict:
return False return False
assert prefix != self._prefix, self.__dict__ assert prefix != self._prefix, self.__dict__
address = [x for x in utils.parse_address(address)
if x[2] not in self._disable_proto]
self._peer_db.connecting(prefix, 1)
if not address:
return False
logging.info('Establishing a connection with %u/%u', logging.info('Establishing a connection with %u/%u',
int(prefix, 2), len(prefix)) int(prefix, 2), len(prefix))
iface = self.getFreeInterface(prefix) iface = self.getFreeInterface(prefix)
...@@ -224,7 +229,6 @@ class TunnelManager(object): ...@@ -224,7 +229,6 @@ class TunnelManager(object):
for ip in c: for ip in c:
self._gateway_manager.add(ip, True) self._gateway_manager.add(ip, True)
c.open(self._write_pipe, self._timeout, self._encrypt, self._ovpn_args) c.open(self._write_pipe, self._timeout, self._encrypt, self._ovpn_args)
self._peer_db.connecting(prefix, 1)
return True return True
def _makeNewTunnels(self, route_counted): def _makeNewTunnels(self, route_counted):
...@@ -435,10 +439,13 @@ class TunnelManager(object): ...@@ -435,10 +439,13 @@ class TunnelManager(object):
return return
code = ord(msg[0]) code = ord(msg[0])
if code == 1: # answer if code == 1: # answer
# TODO: do not fail if message contains garbage
# We parse the message in a way to discard a truncated line. # We parse the message in a way to discard a truncated line.
for peer in msg[1:].split('\n')[:-1]: for peer in msg[1:].split('\n')[:-1]:
try:
prefix, address = peer.split() prefix, address = peer.split()
int(prefix, 2)
except ValueError:
break
if prefix != self._prefix: if prefix != self._prefix:
self._peer_db.addPeer(prefix, address) self._peer_db.addPeer(prefix, address)
try: try:
......
import argparse, errno, logging, os, shlex, signal, socket import argparse, calendar, errno, logging, os, shlex, signal, socket
import struct, subprocess, textwrap, threading, time import struct, subprocess, sys, textwrap, threading, time, traceback
from OpenSSL import crypto
logging_levels = logging.WARNING, logging.INFO, logging.DEBUG, 5 logging_levels = logging.WARNING, logging.INFO, logging.DEBUG, 5
...@@ -45,6 +44,10 @@ def setupLog(log_level, filename=None, **kw): ...@@ -45,6 +44,10 @@ def setupLog(log_level, filename=None, **kw):
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)
def log_exception():
f = traceback.format_exception(*sys.exc_info())
logging.error('%s%s', f.pop(), ''.join(f))
class HelpFormatter(argparse.ArgumentDefaultsHelpFormatter): class HelpFormatter(argparse.ArgumentDefaultsHelpFormatter):
...@@ -128,13 +131,14 @@ def ipFromBin(ip, suffix=''): ...@@ -128,13 +131,14 @@ def ipFromBin(ip, suffix=''):
struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2))) struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2)))
def networkFromCa(ca): def networkFromCa(ca):
ca = crypto.load_certificate(crypto.FILETYPE_PEM, ca)
return bin(ca.get_serial_number())[3:] return bin(ca.get_serial_number())[3:]
def subnetFromCert(cert): def subnetFromCert(cert):
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
return cert.get_subject().CN return cert.get_subject().CN
def notAfter(cert):
return calendar.timegm(time.strptime(cert.get_notAfter(),'%Y%m%d%H%M%SZ'))
def dump_address(address): def dump_address(address):
return ';'.join(map(','.join, address)) return ';'.join(map(','.join, address))
...@@ -150,3 +154,27 @@ def parse_address(address_list): ...@@ -150,3 +154,27 @@ def parse_address(address_list):
def binFromSubnet(subnet): def binFromSubnet(subnet):
p, l = subnet.split('/') p, l = subnet.split('/')
return bin(int(p))[2:].rjust(int(l), '0') return bin(int(p))[2:].rjust(int(l), '0')
def decrypt(key_path, data):
p = subprocess.Popen(
('openssl', 'rsautl', '-decrypt', '-inkey', key_path),
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
out, err = p.communicate(data)
if p.returncode:
raise subprocess.CalledProcessError(p.returncode, 'openssl', err)
return out
def encrypt(cert, data):
r, w = os.pipe()
try:
threading.Thread(target=os.write, args=(w, cert)).start()
p = subprocess.Popen(('openssl', 'rsautl', '-encrypt', '-certin',
'-inkey', '/proc/self/fd/%u' % r),
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
out, err = p.communicate(data)
finally:
os.close(r)
os.close(w)
if p.returncode:
raise subprocess.CalledProcessError(p.returncode, 'openssl', err)
return out
#!/usr/bin/python #!/usr/bin/python
import atexit, errno, logging, os, select, signal import atexit, errno, logging, os, select, signal
import sqlite3, subprocess, sys, time, traceback import sqlite3, subprocess, sys, time, threading
from collections import deque from collections import deque
from re6st import plib, utils, db, tunnel from OpenSSL import crypto
from re6st import db, plib, tunnel, utils
from re6st.registry import RegistryClient, RENEW_PERIOD
class ReexecException(Exception):
pass
def getConfig(): def getConfig():
parser = utils.ArgParser(fromfile_prefix_chars='@', parser = utils.ArgParser(fromfile_prefix_chars='@',
...@@ -61,12 +65,14 @@ def getConfig(): ...@@ -61,12 +65,14 @@ def getConfig():
" hello interval for Babel to re-establish connection with a" " hello interval for Babel to re-establish connection with a"
" node for which the direct connection has been cut.") " node for which the direct connection has been cut.")
_('--table', type=int, default=42, _('--table', type=int, default=42,
help="Use given table id. Set 0 to use the main table, if:\n" help="Use given table id. Set 0 to use the main table, if you want to"
"- you are a gateway of this network (the default route will be" " access internet via this network (in this case, make sure you"
" exported)\n" " don't already have a default route). Don't use this option with"
"- or you want to use the default route of this network for all" " --gateway (main table is automatically used).")
" communications (in this case, make sure you don't already have" _('--gateway', action='store_true',
" a default route).\n") help="Act as a gateway for this network (the default route will be"
" exported). Do never use it if you don't know what it means.")
_ = parser.add_argument_group('tunnelling').add_argument _ = parser.add_argument_group('tunnelling').add_argument
_('-O', dest='openvpn_args', metavar='ARG', action='append', default=[], _('-O', dest='openvpn_args', metavar='ARG', action='append', default=[],
help="Extra arguments to forward to both server and client OpenVPN" help="Extra arguments to forward to both server and client OpenVPN"
...@@ -101,6 +107,8 @@ def getConfig(): ...@@ -101,6 +107,8 @@ def getConfig():
_('--remote-gateway', action='append', dest='gw_list', _('--remote-gateway', action='append', dest='gw_list',
help="Force each tunnel to be created through one the given gateways," help="Force each tunnel to be created through one the given gateways,"
" in a round-robin fashion.") " in a round-robin fashion.")
_('--disable-proto', action='append', choices=('udp', 'tcp'), default=[],
help="Do never try to create tunnels using given protocols.")
_('--client', metavar='HOST,PORT,PROTO[;...]', _('--client', metavar='HOST,PORT,PROTO[;...]',
help="Do not run any OpenVPN server, but only 1 OpenVPN client," help="Do not run any OpenVPN server, but only 1 OpenVPN client,"
" with specified remotes. Any other option not required in this" " with specified remotes. Any other option not required in this"
...@@ -108,14 +116,43 @@ def getConfig(): ...@@ -108,14 +116,43 @@ def getConfig():
return parser.parse_args() return parser.parse_args()
def maybe_renew(path, cert, info, renew):
while True:
next_renew = utils.notAfter(cert) - RENEW_PERIOD
if time.time() < next_renew:
return cert, next_renew
try:
pem = renew()
if not pem or pem == crypto.dump_certificate(
crypto.FILETYPE_PEM, cert):
exc_info = 0
break
cert = crypto.load_certificate(crypto.FILETYPE_PEM, pem)
except Exception:
exc_info = 1
break
new_path = path + '.new'
with open(new_path, 'w') as f:
f.write(pem)
os.rename(new_path, path)
logging.info("%s renewed until %s UTC",
info, time.asctime(time.gmtime(utils.notAfter(cert))))
logging.error("%s not renewed. Will retry tomorrow.",
info, exc_info=exc_info)
return cert, time.time() + 86400
def exit(status):
exit.status = status
os.kill(os.getpid(), signal.SIGTERM)
def main(): def main():
# Get arguments # Get arguments
config = getConfig() config = getConfig()
with open(config.ca) as f: with open(config.ca) as f:
network = utils.networkFromCa(f.read()) ca = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
with open(config.cert) as f: with open(config.cert) as f:
prefix = utils.binFromSubnet(utils.subnetFromCert(f.read())) cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
prefix = utils.binFromSubnet(utils.subnetFromCert(cert))
config.openvpn_args += ( config.openvpn_args += (
'--ca', config.ca, '--ca', config.ca,
'--cert', config.cert, '--cert', config.cert,
...@@ -136,7 +173,16 @@ def main(): ...@@ -136,7 +173,16 @@ def main():
plib.ovpn_log = config.log plib.ovpn_log = config.log
signal.signal(signal.SIGHUP, lambda *args: sys.exit(-1)) signal.signal(signal.SIGHUP, lambda *args: sys.exit(-1))
signal.signal(signal.SIGTERM, lambda *args: sys.exit()) signal.signal(signal.SIGTERM, lambda *args:
sys.exit(getattr(exit, 'status', None)))
registry = RegistryClient(config.registry, config.key, ca)
cert, next_renew = maybe_renew(config.cert, cert, "Certificate",
lambda: registry.renewCertificate(prefix))
ca, ca_renew = maybe_renew(config.ca, ca, "CA Certificate", registry.getCa)
if next_renew > ca_renew:
next_renew = ca_renew
network = utils.networkFromCa(ca)
if config.max_clients is None: if config.max_clients is None:
config.max_clients = config.client_count * 2 config.max_clients = config.client_count * 2
...@@ -220,21 +266,23 @@ def main(): ...@@ -220,21 +266,23 @@ def main():
# Init db and tunnels # Init db and tunnels
tunnel_interfaces = server_tunnels.keys() tunnel_interfaces = server_tunnels.keys()
timeout = 4 * config.hello timeout = 4 * config.hello
cleanup = []
if config.client_count and not config.client: if config.client_count and not config.client:
required('registry') required('registry')
# Create and open read_only pipe to get server events # Create and open read_only pipe to get 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)
peer_db = db.PeerDB(db_path, config.registry, config.key, prefix) peer_db = db.PeerDB(db_path, registry, config.key, network, prefix)
tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db,
config.openvpn_args, timeout, config.tunnel_refresh, config.openvpn_args, timeout, config.tunnel_refresh,
config.client_count, config.iface_list, network, prefix, config.client_count, config.iface_list, network, prefix,
address, ip_changed, config.encrypt, remote_gateway) address, ip_changed, config.encrypt, remote_gateway,
config.disable_proto)
cleanup.append(tunnel_manager.sock.close)
tunnel_interfaces += tunnel_manager.new_iface_list tunnel_interfaces += tunnel_manager.new_iface_list
else: else:
tunnel_manager = write_pipe = None tunnel_manager = write_pipe = None
cleanup = []
try: try:
# Source address selection is defined by RFC 6724, and in most # Source address selection is defined by RFC 6724, and in most
# applications, it usually works thanks to rule 5 (prefer outgoing # applications, it usually works thanks to rule 5 (prefer outgoing
...@@ -277,7 +325,9 @@ def main(): ...@@ -277,7 +325,9 @@ def main():
cleanup.append(lambda: subprocess.call(if_rt)) cleanup.append(lambda: subprocess.call(if_rt))
x = [my_network] x = [my_network]
if config.table: if config.gateway:
config.table = 0
elif config.table:
x += 'table', str(config.table) x += 'table', str(config.table)
try: try:
ip('rule', 'from', *x) ip('rule', 'from', *x)
...@@ -292,6 +342,26 @@ def main(): ...@@ -292,6 +342,26 @@ def main():
if_rt += x[1:] if_rt += x[1:]
call(if_rt[:3] + ['add', 'proto', 'static'] + if_rt[4:]) call(if_rt[:3] + ['add', 'proto', 'static'] + if_rt[4:])
else:
def check_no_default_route():
try:
while True:
for route in call(('ip', '-6', 'route', 'show',
'default')).splitlines():
if ' proto 42 ' not in route:
logging.fatal("Detected default route (%s)"
" whereas you specified --table=0."
" Fix your configuration.", route)
return
time.sleep(60)
except:
utils.log_exception()
finally:
exit(1)
t = threading.Thread(target=check_no_default_route)
t.daemon = True
t.start()
# adding tap-windows driver will break others, so we add # adding tap-windows driver will break others, so we add
# all drivers here # all drivers here
if sys.platform == 'cygwin': if sys.platform == 'cygwin':
...@@ -314,7 +384,8 @@ def main(): ...@@ -314,7 +384,8 @@ def main():
# main loop # main loop
if tunnel_manager is None: if tunnel_manager is None:
sys.exit(os.WEXITSTATUS(os.wait()[1])) time.sleep(max(0, next_renew - time.time()))
raise ReexecException("Restart to renew certificate")
cleanup += tunnel_manager.delInterfaces, tunnel_manager.killAll cleanup += tunnel_manager.delInterfaces, tunnel_manager.killAll
while True: while True:
next = tunnel_manager.next_refresh next = tunnel_manager.next_refresh
...@@ -334,6 +405,8 @@ def main(): ...@@ -334,6 +405,8 @@ def main():
t = time.time() t = time.time()
if t >= tunnel_manager.next_refresh: if t >= tunnel_manager.next_refresh:
tunnel_manager.refresh() tunnel_manager.refresh()
if t >= next_renew:
raise ReexecException("Restart to renew certificate")
if forwarder and t >= forwarder.next_refresh: if forwarder and t >= forwarder.next_refresh:
forwarder.refresh() forwarder.refresh()
finally: finally:
...@@ -345,16 +418,17 @@ def main(): ...@@ -345,16 +418,17 @@ def main():
except sqlite3.Error: except sqlite3.Error:
logging.exception("Restarting with empty cache") logging.exception("Restarting with empty cache")
os.rename(db_path, db_path + '.bak') os.rename(db_path, db_path + '.bak')
try: except ReexecException, e:
sys.exitfunc() logging.info(e)
finally:
os.execvp(sys.argv[0], sys.argv)
except KeyboardInterrupt: except KeyboardInterrupt:
return 0 return 0
except Exception: except Exception:
f = traceback.format_exception(*sys.exc_info()) utils.log_exception()
logging.error('%s%s', f.pop(), ''.join(f))
sys.exit(1) sys.exit(1)
try:
sys.exitfunc()
finally:
os.execvp(sys.argv[0], sys.argv)
if __name__ == "__main__": if __name__ == "__main__":
main() main()
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