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, """\
......
#!/usr/bin/python #!/usr/bin/python
import errno, logging, mailbox, os, random, select import errno, httplib, logging, select, socket
import smtplib, socket, sqlite3, string, subprocess, sys from BaseHTTPServer import BaseHTTPRequestHandler
import threading, time, traceback, xmlrpclib from SocketServer import ThreadingTCPServer
from collections import deque from urlparse import parse_qsl
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler from re6st import registry, utils
from email.mime.text import MIMEText
from OpenSSL import crypto
from re6st import tunnel, utils
# To generate server ca and key with serial for 2001:db8:42::/48 # To generate server ca and key with serial for 2001:db8:42::/48
# openssl req -nodes -new -x509 -key ca.key -set_serial 0x120010db80042 -days 365 -out ca.crt # openssl req -nodes -new -x509 -key ca.key -set_serial 0x120010db80042 -days 3650 -out ca.crt
IPV6_V6ONLY = 26 IPV6_V6ONLY = 26
SOL_IPV6 = 41 SOL_IPV6 = 41
class RequestHandler(SimpleXMLRPCRequestHandler): class RequestHandler(BaseHTTPRequestHandler):
def address_string(self): def address_string(self):
# Workaround for http://bugs.python.org/issue6085 # Workaround for http://bugs.python.org/issue6085
return self.client_address[0] return self.client_address[0]
def _dispatch(self, method, params): def do_GET(self):
logging.debug('%s%r', method, params) try:
return self.server._dispatch(method, (self,) + params) try:
path, query = self.path.split('?', 1)
except ValueError:
path = self.path
query = {}
else:
query = dict(parse_qsl(query, keep_blank_values=1,
strict_parsing=1))
_, path = path.split('/')
if not _ and path[0] != '_':
return self.server._handle_request(self, path, query)
except Exception:
logging.info(self.requestline, exc_info=1)
self.send_error(httplib.BAD_REQUEST)
def log_error(*args):
pass
class SimpleXMLRPCServer4(SimpleXMLRPCServer): class HTTPServer4(ThreadingTCPServer):
allow_reuse_address = True allow_reuse_address = True
daemon_threads = True
class SimpleXMLRPCServer6(SimpleXMLRPCServer4): class HTTPServer6(HTTPServer4):
address_family = socket.AF_INET6 address_family = socket.AF_INET6
def server_bind(self): def server_bind(self):
self.socket.setsockopt(SOL_IPV6, IPV6_V6ONLY, 1) self.socket.setsockopt(SOL_IPV6, IPV6_V6ONLY, 1)
SimpleXMLRPCServer4.server_bind(self) HTTPServer4.server_bind(self)
class main(object): def main():
parser = utils.ArgParser(fromfile_prefix_chars='@',
def __init__(self): description="re6stnet registry used to bootstrap nodes"
self.cert_duration = 365 * 86400 " and deliver certificates.")
self.time_out = 45000 _ = parser.add_argument
self.refresh_interval = 600 _('--port', type=int, default=80,
self.last_refresh = time.time() help="Port on which the server will listen.")
_('-4', dest='bind4', default='0.0.0.0',
help="Bind server to this IPv4.")
# Command line parsing _('-6', dest='bind6', default='::',
parser = utils.ArgParser(fromfile_prefix_chars='@', help="Bind server to this IPv6.")
description="re6stnet registry used to bootstrap nodes" _('--db', default='/var/lib/re6stnet/registry.db',
" and deliver certificates.") help="Path to SQLite database file. It is automatically initialized"
_ = parser.add_argument " if the file does not exist.")
_('--port', type=int, default=80, _('--ca', required=True, help=parser._ca_help)
help="Port on which the server will listen.") _('--key', required=True,
_('-4', dest='bind4', default='0.0.0.0', help="CA private key in .pem format.")
help="Bind server to this IPv4.") _('--mailhost', required=True,
_('-6', dest='bind6', default='::', help="SMTP host to send confirmation emails. For debugging"
help="Bind server to this IPv6.") " purpose, it can also be an absolute or existing path to"
_('--db', default='/var/lib/re6stnet/registry.db', " a mailbox file")
help="Path to SQLite database file. It is automatically initialized" _('--private',
" if the file does not exist.") help="re6stnet IP of the node on which runs the registry."
_('--ca', required=True, help=parser._ca_help) " Required for normal operation.")
_('--key', required=True, _('--prefix-length', default=16, type=int,
help="CA private key in .pem format.") help="Default length of allocated prefixes.")
_('--mailhost', required=True, _('--anonymous-prefix-length', type=int,
help="SMTP host to send confirmation emails. For debugging" help="Length of allocated anonymous prefixes."
" purpose, it can also be an absolute or existing path to" " If 0 or unset, registration by email is required")
" a mailbox file") _('-l', '--logfile', default='/var/log/re6stnet/registry.log',
_('--private', help="Path to logging file.")
help="re6stnet IP of the node on which runs the registry." _('-v', '--verbose', default=1, type=int,
" Required for normal operation.") help="Log level. 0 disables logging."
_('--prefix-length', default=16, type=int, " Use SIGUSR1 to reopen log.")
help="Default length of allocated prefixes.") config = parser.parse_args()
_('--anonymous-prefix-length', type=int,
help="Length of allocated anonymous prefixes." utils.setupLog(config.verbose, config.logfile)
" If 0 or unset, registration by email is required")
_('-l', '--logfile', default='/var/log/re6stnet/registry.log', server = registry.RegistryServer(config)
help="Path to logging file.") def requestHandler(request, client_address, _):
_('-v', '--verbose', default=1, type=int, RequestHandler(request, client_address, server)
help="Log level. 0 disables logging."
" Use SIGUSR1 to reopen log.") server_list = []
self.config = parser.parse_args() if config.bind4:
server_list.append(HTTPServer4((config.bind4, config.port),
utils.setupLog(self.config.verbose, self.config.logfile) requestHandler))
if config.bind6:
if self.config.private: server_list.append(HTTPServer6((config.bind6, config.port),
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) requestHandler))
else: if server_list:
logging.warning('You have declared no private address' empty_list = []
', either this is the first start, or you should'
'check you configuration')
# Database initializing
utils.makedirs(os.path.dirname(self.config.db))
self.db = sqlite3.connect(self.config.db, isolation_level=None)
self.db.execute("""CREATE TABLE IF NOT EXISTS token (
token text primary key not null,
email text not null,
prefix_len integer not null,
date integer not null)""")
try:
self.db.execute("""CREATE TABLE cert (
prefix text primary key not null,
email text,
cert text)""")
except sqlite3.OperationalError, e:
if e.args[0] != 'table cert already exists':
raise RuntimeError
else:
self.db.execute("INSERT INTO cert VALUES ('',null,null)")
# Loading certificates
with open(self.config.ca) as f:
self.ca = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
with open(self.config.key) as f:
self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
# Get vpn network prefix
self.network = bin(self.ca.get_serial_number())[3:]
logging.info("Network: %s/%u", utils.ipFromBin(self.network),
len(self.network))
self._email = self.ca.get_subject().emailAddress
# Starting server
server_list = []
if self.config.bind4:
server4 = SimpleXMLRPCServer4((self.config.bind4, self.config.port),
requestHandler=RequestHandler, allow_none=True)
server4.register_instance(self)
server_list.append(server4)
if self.config.bind6:
server6 = SimpleXMLRPCServer6((self.config.bind6, self.config.port),
requestHandler=RequestHandler, allow_none=True)
server6.register_instance(self)
server_list.append(server6)
if len(server_list) == 1:
server_list[0].serve_forever()
else:
while True:
try:
r = select.select(server_list[:], [], [])[0]
except select.error as e:
if e.args[0] != errno.EINTR:
raise
else:
for r in r:
r._handle_request_noblock()
def requestToken(self, handler, email):
while True: while True:
# Generating token
token = ''.join(random.sample(string.ascii_lowercase, 8))
args = token, email, self.config.prefix_length, int(time.time())
# Updating database
try:
self.db.execute("INSERT INTO token VALUES (?,?,?,?)", args)
break
except sqlite3.IntegrityError:
pass
# Creating and sending email
msg = MIMEText('Hello, your token to join re6st network is: %s\n'
% token)
msg['Subject'] = '[re6stnet] Token Request'
if self._email:
msg['From'] = self._email
msg['To'] = email
if os.path.isabs(self.config.mailhost) or \
os.path.isfile(self.config.mailhost):
m = mailbox.mbox(self.config.mailhost)
try: try:
m.add(msg) r = select.select(server_list[:], empty_list, empty_list)[0]
finally: except select.error as e:
m.close() if e.args[0] != errno.EINTR:
else: raise
s = smtplib.SMTP(self.config.mailhost) else:
s.sendmail(self._email, email, msg.as_string()) for r in r:
s.quit() r._handle_request_noblock()
def _getPrefix(self, prefix_len):
max_len = 128 - len(self.network)
assert 0 < prefix_len <= max_len
try:
prefix, = self.db.execute("""SELECT prefix FROM cert WHERE length(prefix) <= ? AND cert is null
ORDER BY length(prefix) DESC""", (prefix_len,)).next()
except StopIteration:
logging.error('No more free /%u prefix available', prefix_len)
raise
while len(prefix) < prefix_len:
self.db.execute("UPDATE cert SET prefix = ? WHERE prefix = ?", (prefix + '1', prefix))
prefix += '0'
self.db.execute("INSERT INTO cert VALUES (?,null,null)", (prefix,))
if len(prefix) < max_len or '1' in prefix:
return prefix
self.db.execute("UPDATE cert SET cert = 'reserved' WHERE prefix = ?", (prefix,))
return self._getPrefix(prefix_len)
def requestCertificate(self, handler, token, cert_req):
try:
req = crypto.load_certificate_request(crypto.FILETYPE_PEM, cert_req)
with self.db:
if token is None:
prefix_len = self.config.anonymous_prefix_length
if not prefix_len:
return
email = None
else:
try:
token, email, prefix_len, _ = self.db.execute(
"SELECT * FROM token WHERE token = ?",
(token,)).next()
except StopIteration:
return
self.db.execute("DELETE FROM token WHERE token = ?", (token,))
# Get a new prefix
prefix = self._getPrefix(prefix_len)
# Create certificate
cert = crypto.X509()
cert.set_serial_number(0) # required for libssl < 1.0
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(self.cert_duration)
cert.set_issuer(self.ca.get_subject())
subject = req.get_subject()
subject.CN = "%u/%u" % (int(prefix, 2), prefix_len)
cert.set_subject(subject)
cert.set_pubkey(req.get_pubkey())
cert.sign(self.key, 'sha1')
cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
# Insert certificate into db
self.db.execute("UPDATE cert SET email = ?, cert = ? WHERE prefix = ?", (email, cert, prefix))
return cert
except Exception:
f = traceback.format_exception(*sys.exc_info())
logging.error('%s%s', f.pop(), ''.join(f))
raise
def getCa(self, handler):
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.ca)
def getPrivateAddress(self, handler):
return self.config.private
def getBootstrapPeer(self, handler, client_prefix):
cert, = self.db.execute("SELECT cert FROM cert WHERE prefix = ?",
(client_prefix,)).next()
address = self.config.private, tunnel.PORT
self.sock.sendto('\2', address)
peer = None
while select.select([self.sock], [], [], peer is None)[0]:
msg = self.sock.recv(1<<16)
if msg[0] == '\1':
try:
peer = msg[1:].split('\n')[-2]
except IndexError:
peer = ''
if peer is None:
raise EnvironmentError("Timeout while querying [%s]:%u" % address)
if not peer or peer.split()[0] == client_prefix:
raise LookupError("No bootstrap peer found")
logging.info("Sending bootstrap peer: %s", peer)
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)
return xmlrpclib.Binary(p.communicate(peer)[0])
finally:
os.close(r)
os.close(w)
def topology(self, handler):
if handler.client_address[0] in ('127.0.0.1', '::'):
is_registry = utils.binFromIp(self.config.private
)[len(self.network):].startswith
peers = deque('%u/%u' % (int(x, 2), len(x))
for x, in self.db.execute("SELECT prefix FROM cert")
if is_registry(x))
assert len(peers) == 1
cookie = hex(random.randint(0, 1<<32))[2:]
graph = dict.fromkeys(peers)
asked = 0
while True:
r, w, _ = select.select([self.sock],
[self.sock] if peers else [], [], 1)
if r:
answer = self.sock.recv(1<<16)
if answer[0] == '\xfe':
answer = answer[1:].split('\n')[:-1]
if len(answer) >= 3 and answer[0] == cookie:
x = answer[3:]
assert answer[1] not in x, (answer, graph)
graph[answer[1]] = x[:int(answer[2])]
x = set(x).difference(graph)
peers += x
graph.update(dict.fromkeys(x))
if w:
x = utils.binFromSubnet(peers.popleft())
x = utils.ipFromBin(self.network + x)
try:
self.sock.sendto('\xff%s\n' % cookie, (x, tunnel.PORT))
except socket.error:
pass
elif not r:
break
return graph
if __name__ == "__main__": if __name__ == "__main__":
main() main()
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,28 +25,36 @@ class PeerDB(object): ...@@ -25,28 +25,36 @@ 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:
retry = 1 self.registry_ip = utils.binFromIp(a)
while True: if not self.registry_ip.startswith(network):
try: a = self._updateRegistryIP()
a = self._proxy.getPrivateAddress()
break
except socket.error, e:
logging.warning(e)
time.sleep(retry)
retry = min(60, retry * 2)
q("INSERT INTO config VALUES ('registry',?)", (a,))
self.registry_ip = utils.binFromIp(a)
logging.info("Cache initialized. Registry IP is %s", a) logging.info("Cache initialized. Registry IP is %s", a)
def _updateRegistryIP(self):
logging.info("Asking registry its private IP...")
retry = 1
while True:
try:
a = self._registry.getPrivateAddress(self._prefix)
break
except socket.error, e:
logging.warning(e)
time.sleep(retry)
retry = min(60, retry * 2)
self._db.execute("INSERT OR REPLACE INTO config VALUES ('registry',?)",
(a,))
self.registry_ip = utils.binFromIp(a)
return a
def log(self): def log(self):
if logging.getLogger().isEnabledFor(5): if logging.getLogger().isEnabledFor(5):
logging.trace("Cache:") logging.trace("Cache:")
...@@ -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,)) if a:
a = True qq = self._db.executemany
if a: 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,))
import base64, hmac, hashlib, httplib, inspect, logging, mailbox, os, random
import select, smtplib, socket, sqlite3, string, struct, threading, time
from collections import deque
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from email.mime.text import MIMEText
from OpenSSL import crypto
from urllib import splittype, splithost, splitport, urlencode
from . import tunnel, utils
HMAC_HEADER = "Re6stHMAC"
RENEW_PERIOD = 30 * 86400
class getcallargs(type):
def __init__(cls, name, bases, d):
type.__init__(cls, name, bases, d)
for n, f in d.iteritems():
if n[0] == '_':
continue
try:
args, varargs, varkw, defaults = inspect.getargspec(f)
except TypeError:
continue
if varargs or varkw or defaults:
continue
f.getcallargs = eval("lambda %s: locals()" % ','.join(args[1:]))
class RegistryServer(object):
__metaclass__ = getcallargs
def __init__(self, config):
self.config = config
self.cert_duration = 365 * 86400
self.lock = threading.Lock()
self.sessions = {}
if self.config.private:
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
else:
logging.warning('You have declared no private address'
', either this is the first start, or you should'
'check you configuration')
# Database initializing
utils.makedirs(os.path.dirname(self.config.db))
self.db = sqlite3.connect(self.config.db, isolation_level=None,
check_same_thread=False)
self.db.execute("""CREATE TABLE IF NOT EXISTS token (
token text primary key not null,
email text not null,
prefix_len integer not null,
date integer not null)""")
try:
self.db.execute("""CREATE TABLE cert (
prefix text primary key not null,
email text,
cert text)""")
except sqlite3.OperationalError, e:
if e.args[0] != 'table cert already exists':
raise RuntimeError
else:
self.db.execute("INSERT INTO cert VALUES ('',null,null)")
# Loading certificates
with open(self.config.ca) as f:
self.ca = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
with open(self.config.key) as f:
self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
# Get vpn network prefix
self.network = utils.networkFromCa(self.ca)
logging.info("Network: %s/%u", utils.ipFromBin(self.network),
len(self.network))
self._email = self.ca.get_subject().emailAddress
def _handle_request(self, request, method, kw):
m = getattr(self, method)
if method in ('topology',) and \
request.client_address[0] not in ('127.0.0.1', '::'):
return request.send_error(httplib.FORBIDDEN)
key = m.getcallargs(**kw).get('cn')
if key:
h = base64.b64decode(request.headers[HMAC_HEADER])
with self.lock:
session = self.sessions[key]
for key in session:
if h == hmac.HMAC(key, request.path, hashlib.sha1).digest():
break
else:
raise Exception("Wrong HMAC")
key = hashlib.sha1(key).digest()
session[:] = hashlib.sha1(key).digest(),
try:
result = m(**kw)
except:
logging.warning(request.requestline, exc_info=1)
return request.send_error(httplib.INTERNAL_SERVER_ERROR)
if result:
request.send_response(httplib.OK)
request.send_header("Content-Length", str(len(result)))
else:
request.send_response(httplib.NO_CONTENT)
if key:
request.send_header(HMAC_HEADER, base64.b64encode(
hmac.HMAC(key, result, hashlib.sha1).digest()))
request.end_headers()
if result:
request.wfile.write(result)
def hello(self, client_prefix):
with self.lock:
cert = self._getCert(client_prefix)
key = hashlib.sha1(struct.pack('Q',
random.getrandbits(64))).digest()
self.sessions.setdefault(client_prefix, [])[1:] = key,
key = utils.encrypt(cert, key)
sign = crypto.sign(self.key, key, 'sha1')
assert len(key) == len(sign)
return key + sign
def _getCert(self, client_prefix):
assert self.lock.locked()
return self.db.execute("SELECT cert FROM cert WHERE prefix = ?",
(client_prefix,)).next()[0]
def requestToken(self, email):
while True:
# Generating token
token = ''.join(random.sample(string.ascii_lowercase, 8))
args = token, email, self.config.prefix_length, int(time.time())
# Updating database
try:
self.db.execute("INSERT INTO token VALUES (?,?,?,?)", args)
break
except sqlite3.IntegrityError:
pass
# Creating and sending email
msg = MIMEText('Hello, your token to join re6st network is: %s\n'
% token)
msg['Subject'] = '[re6stnet] Token Request'
if self._email:
msg['From'] = self._email
msg['To'] = email
if os.path.isabs(self.config.mailhost) or \
os.path.isfile(self.config.mailhost):
with self.lock:
m = mailbox.mbox(self.config.mailhost)
try:
m.add(msg)
finally:
m.close()
else:
s = smtplib.SMTP(self.config.mailhost)
s.sendmail(self._email, email, msg.as_string())
s.quit()
def _getPrefix(self, prefix_len):
max_len = 128 - len(self.network)
assert 0 < prefix_len <= max_len
try:
prefix, = self.db.execute("""SELECT prefix FROM cert WHERE length(prefix) <= ? AND cert is null
ORDER BY length(prefix) DESC""", (prefix_len,)).next()
except StopIteration:
logging.error('No more free /%u prefix available', prefix_len)
raise
while len(prefix) < prefix_len:
self.db.execute("UPDATE cert SET prefix = ? WHERE prefix = ?", (prefix + '1', prefix))
prefix += '0'
self.db.execute("INSERT INTO cert VALUES (?,null,null)", (prefix,))
if len(prefix) < max_len or '1' in prefix:
return prefix
self.db.execute("UPDATE cert SET cert = 'reserved' WHERE prefix = ?", (prefix,))
return self._getPrefix(prefix_len)
def requestCertificate(self, token, req):
req = crypto.load_certificate_request(crypto.FILETYPE_PEM, req)
with self.lock:
with self.db:
if token is None:
prefix_len = self.config.anonymous_prefix_length
if not prefix_len:
return
email = None
else:
try:
token, email, prefix_len, _ = self.db.execute(
"SELECT * FROM token WHERE token = ?",
(token,)).next()
except StopIteration:
return
self.db.execute("DELETE FROM token WHERE token = ?",
(token,))
prefix = self._getPrefix(prefix_len)
self.db.execute("UPDATE cert SET email = ? WHERE prefix = ?",
(email, prefix))
return self._createCertificate(prefix, req.get_subject(),
req.get_pubkey())
def _createCertificate(self, client_prefix, subject, pubkey):
cert = crypto.X509()
cert.set_serial_number(0) # required for libssl < 1.0
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(self.cert_duration)
cert.set_issuer(self.ca.get_subject())
subject.CN = "%u/%u" % (int(client_prefix, 2), len(client_prefix))
cert.set_subject(subject)
cert.set_pubkey(pubkey)
cert.sign(self.key, 'sha1')
cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
self.db.execute("UPDATE cert SET cert = ? WHERE prefix = ?",
(cert, client_prefix))
return cert
def renewCertificate(self, cn):
with self.lock:
with self.db:
pem = self._getCert(cn)
cert = crypto.load_certificate(crypto.FILETYPE_PEM, pem)
if utils.notAfter(cert) - RENEW_PERIOD < time.time():
pem = self._createCertificate(cn, cert.get_subject(),
cert.get_pubkey())
return pem
def getCa(self):
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.ca)
def getPrivateAddress(self, cn):
return self.config.private
def getBootstrapPeer(self, cn):
with self.lock:
cert = self._getCert(cn)
address = self.config.private, tunnel.PORT
self.sock.sendto('\2', address)
peer = None
while select.select([self.sock], [], [], peer is None)[0]:
msg = self.sock.recv(1<<16)
if msg[0] == '\1':
try:
peer = msg[1:].split('\n')[-2]
except IndexError:
peer = ''
if peer is None:
raise EnvironmentError("Timeout while querying [%s]:%u" % address)
if not peer or peer.split()[0] == cn:
raise LookupError("No bootstrap peer found")
logging.info("Sending bootstrap peer: %s", peer)
return utils.encrypt(cert, peer)
def topology(self):
with self.lock:
is_registry = utils.binFromIp(self.config.private
)[len(self.network):].startswith
peers = deque('%u/%u' % (int(x, 2), len(x))
for x, in self.db.execute("SELECT prefix FROM cert")
if is_registry(x))
assert len(peers) == 1
cookie = hex(random.randint(0, 1<<32))[2:]
graph = dict.fromkeys(peers)
asked = 0
while True:
r, w, _ = select.select([self.sock],
[self.sock] if peers else [], [], 1)
if r:
answer = self.sock.recv(1<<16)
if answer[0] == '\xfe':
answer = answer[1:].split('\n')[:-1]
if len(answer) >= 3 and answer[0] == cookie:
x = answer[3:]
assert answer[1] not in x, (answer, graph)
graph[answer[1]] = x[:int(answer[2])]
x = set(x).difference(graph)
peers += x
graph.update(dict.fromkeys(x))
if w:
x = utils.binFromSubnet(peers.popleft())
x = utils.ipFromBin(self.network + x)
try:
self.sock.sendto('\xff%s\n' % cookie, (x, tunnel.PORT))
except socket.error:
pass
elif not r:
break
return repr(graph)
class RegistryClient(object):
_hmac = None
def __init__(self, url, key_path=None, ca=None, auto_close=True):
self.key_path = key_path
self.ca = ca
self.auto_close = auto_close
scheme, host = splittype(url)
host, path = splithost(host)
host, port = splitport(host)
self._conn = dict(http=httplib.HTTPConnection,
https=httplib.HTTPSConnection,
)[scheme](host, port)
self._path = path.rstrip('/')
def __getattr__(self, name):
getcallargs = getattr(RegistryServer, name).getcallargs
def rpc(*args, **kw):
kw = getcallargs(*args, **kw)
query = '/' + name
if kw:
if any(type(v) is not str for v in kw.itervalues()):
raise TypeError
query += '?' + urlencode(kw)
url = self._path + query
client_prefix = kw.get('cn')
retry = True
try:
while retry:
if client_prefix:
key = self._hmac
if not key:
retry = False
h = self.hello(client_prefix)
n = len(h) // 2
crypto.verify(self.ca, h[n:], h[:n], 'sha1')
key = utils.decrypt(self.key_path, h[:n])
h = hmac.HMAC(key, query, hashlib.sha1).digest()
key = hashlib.sha1(key).digest()
self._hmac = hashlib.sha1(key).digest()
else:
retry = False
self._conn.putrequest('GET', url, skip_accept_encoding=1)
if client_prefix:
self._conn.putheader(HMAC_HEADER, base64.b64encode(h))
self._conn.endheaders()
response = self._conn.getresponse()
body = response.read()
if response.status in (httplib.OK, httplib.NO_CONTENT) and (
not client_prefix or
hmac.HMAC(key, body, hashlib.sha1).digest() ==
base64.b64decode(response.msg[HMAC_HEADER])):
if self.auto_close and name != 'hello':
self._conn.close()
return body
if client_prefix:
self._hmac = None
except Exception:
logging.info(url, exc_info=1)
else:
logging.info('%s\nUnexpected response %s %s',
url, response.status, response.reason)
self._conn.close()
setattr(self, name, rpc)
return rpc
...@@ -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]:
prefix, address = peer.split() try:
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