Commit f3ccd7a2 authored by Jondy Zhao's avatar Jondy Zhao

Merge branch 'master' into cygwin

Conflicts:
	re6stnet
parents b04b2b95 6e443638
......@@ -23,3 +23,8 @@
use --main-interface option on it.
- 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'
SCREEN = 'screen'
VERBOSE = 4
REGISTRY='10.0.0.2'
CA_DAYS = 1000
# registry
# |.2
......@@ -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)
if 1:
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'
registry.screen('../re6st-registry @registry/re6st-registry.conf --db %s'
' --mailhost %s -v%u' % (db_path, os.path.abspath('mbox'), VERBOSE))
......@@ -189,7 +194,7 @@ if 1:
p.communicate(str(token[0]))
os.remove(dh_path)
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))
re6stnet(registry, 'registry', '--ip ' + REGISTRY, registry='http://localhost/')
re6stnet(machine1, 'm1', '-I%s' % m1_if_0.name)
......@@ -278,8 +283,9 @@ if len(sys.argv) > 1:
elif self.path == '/tunnel.html':
other = 'route'
gv = registry.Popen(('python', '-c', r"""if 1:
import math, xmlrpclib
g = xmlrpclib.ServerProxy('http://localhost/').topology()
import math
from re6st.registry import RegistryClient
g = eval(RegistryClient('http://localhost/').topology())
print 'digraph {'
a = 2 * math.pi / len(g)
z = 4
......@@ -293,7 +299,7 @@ if len(sys.argv) > 1:
for p in p or ():
print '"%s" -> "%s";' % (n, title(p))
print '}'
"""), stdout=subprocess.PIPE).communicate()[0]
"""), stdout=subprocess.PIPE, cwd="..").communicate()[0]
if gv:
svg = subprocess.Popen(('neato', '-Tsvg'),
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
......
......@@ -7,5 +7,6 @@ dh dh2048.pem
ca ca.crt
cert m1/cert.crt
key m1/cert.key
table 0
client-count 2
tunnel-refresh 100
......@@ -7,5 +7,6 @@ dh dh2048.pem
ca ca.crt
cert m2/cert.crt
key m2/cert.key
table 0
client-count 2
tunnel-refresh 100
......@@ -7,5 +7,6 @@ dh dh2048.pem
ca ca.crt
cert m3/cert.crt
key m3/cert.key
table 0
client-count 2
tunnel-refresh 100
......@@ -7,5 +7,6 @@ dh dh2048.pem
ca ca.crt
cert m4/cert.crt
key m4/cert.key
table 0
client-count 2
tunnel-refresh 100
......@@ -5,5 +5,6 @@ hello 4
ca ca.crt
cert m5/cert.crt
key m5/cert.key
table 0
client-count 0
max-clients 0
......@@ -7,6 +7,7 @@ dh dh2048.pem
ca ca.crt
cert m6/cert.crt
key m6/cert.key
table 0
client-count 2
tunnel-refresh 100
# TODO: Run a DHCPv4 client on machine9. Unfortunately, isc-dhcp-client 4.2.4
......
......@@ -7,5 +7,6 @@ dh dh2048.pem
ca ca.crt
cert m7/cert.crt
key m7/cert.key
table 0
client-count 2
tunnel-refresh 100
......@@ -5,4 +5,5 @@ hello 4
ca ca.crt
cert m8/cert.crt
key m8/cert.key
table 0
client 10.0.1.2,1194,udp;10.0.1.3,1194,udp
......@@ -6,5 +6,6 @@ dh dh2048.pem
ca ca.crt
cert registry/cert.crt
key registry/cert.key
gateway
client-count 2
tunnel-refresh 100
#!/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 re6st import utils
from re6st import registry, utils
def create(path, text=None, mode=0666):
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):
finally:
os.close(fd)
def loadCert(pem):
return crypto.load_certificate(crypto.FILETYPE_PEM, pem)
def main():
parser = argparse.ArgumentParser(
description="Setup script for re6stnet.",
......@@ -47,11 +50,11 @@ def main():
dh_path = 'dh2048.pem'
# Establish connection with server
s = xmlrpclib.ServerProxy(config.registry, allow_none=True)
s = registry.RegistryClient(config.registry)
# Get CA
ca = s.getCa()
network = utils.networkFromCa(ca)
network = utils.networkFromCa(loadCert(ca))
if config.is_needed:
route, err = subprocess.Popen(('ip', '-6', '-o', 'route', 'get',
utils.ipFromBin(network)),
......@@ -72,7 +75,7 @@ def main():
req = crypto.X509Req()
try:
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.pop('CN', None)
except IOError, e:
......@@ -136,7 +139,12 @@ def main():
os.ftruncate(cert_fd, len(cert))
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):
create(conf_path, """\
......
#!/usr/bin/python
import errno, logging, mailbox, os, random, select
import smtplib, socket, sqlite3, string, subprocess, sys
import threading, time, traceback, xmlrpclib
from collections import deque
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
from email.mime.text import MIMEText
from OpenSSL import crypto
from re6st import tunnel, utils
import errno, httplib, logging, select, socket
from BaseHTTPServer import BaseHTTPRequestHandler
from SocketServer import ThreadingTCPServer
from urlparse import parse_qsl
from re6st import registry, utils
# 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
SOL_IPV6 = 41
class RequestHandler(SimpleXMLRPCRequestHandler):
class RequestHandler(BaseHTTPRequestHandler):
def address_string(self):
# Workaround for http://bugs.python.org/issue6085
return self.client_address[0]
def _dispatch(self, method, params):
logging.debug('%s%r', method, params)
return self.server._dispatch(method, (self,) + params)
def do_GET(self):
try:
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
daemon_threads = True
class SimpleXMLRPCServer6(SimpleXMLRPCServer4):
class HTTPServer6(HTTPServer4):
address_family = socket.AF_INET6
def server_bind(self):
self.socket.setsockopt(SOL_IPV6, IPV6_V6ONLY, 1)
SimpleXMLRPCServer4.server_bind(self)
class main(object):
HTTPServer4.server_bind(self)
def __init__(self):
self.cert_duration = 365 * 86400
self.time_out = 45000
self.refresh_interval = 600
self.last_refresh = time.time()
# Command line parsing
def main():
parser = utils.ArgParser(fromfile_prefix_chars='@',
description="re6stnet registry used to bootstrap nodes"
" and deliver certificates.")
......@@ -82,66 +88,26 @@ class main(object):
_('-v', '--verbose', default=1, type=int,
help="Log level. 0 disables logging."
" Use SIGUSR1 to reopen log.")
self.config = parser.parse_args()
config = parser.parse_args()
utils.setupLog(self.config.verbose, self.config.logfile)
utils.setupLog(config.verbose, config.logfile)
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')
server = registry.RegistryServer(config)
def requestHandler(request, client_address, _):
RequestHandler(request, client_address, server)
# 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:
if config.bind4:
server_list.append(HTTPServer4((config.bind4, config.port),
requestHandler))
if config.bind6:
server_list.append(HTTPServer6((config.bind6, config.port),
requestHandler))
if server_list:
empty_list = []
while True:
try:
r = select.select(server_list[:], [], [])[0]
r = select.select(server_list[:], empty_list, empty_list)[0]
except select.error as e:
if e.args[0] != errno.EINTR:
raise
......@@ -149,167 +115,6 @@ class main(object):
for r in r:
r._handle_request_noblock()
def requestToken(self, handler, 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):
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, 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__":
main()
import logging, sqlite3, socket, subprocess, xmlrpclib, time
from urllib import splittype, splithost, splitport
import utils
import logging, sqlite3, socket, subprocess, time
from . import utils
class PeerDB(object):
# 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._db_size = db_size
self._key_path = key_path
self._proxy = xmlrpclib.ServerProxy(registry)
self._registry = registry
logging.info('Initialize cache ...')
self._db = sqlite3.connect(db_path, isolation_level=None)
......@@ -25,27 +25,35 @@ class PeerDB(object):
value text)""")
q('ATTACH DATABASE ":memory:" AS volatile')
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)""")
q("CREATE INDEX volatile.stat_try ON stat(try)")
q("INSERT INTO volatile.stat (peer) SELECT prefix FROM peer")
try:
a = q("SELECT value FROM config WHERE name='registry'").next()[0]
except StopIteration:
logging.info("Private IP of registry not in cache."
" Asking registry via its public IP ...")
a = self._updateRegistryIP()
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
while True:
try:
a = self._proxy.getPrivateAddress()
a = self._registry.getPrivateAddress(self._prefix)
break
except socket.error, e:
logging.warning(e)
time.sleep(retry)
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)
logging.info("Cache initialized. Registry IP is %s", a)
return a
def log(self):
if logging.getLogger().isEnabledFor(5):
......@@ -83,31 +91,41 @@ class PeerDB(object):
def getBootstrapPeer(self):
logging.info('Getting Boot peer...')
try:
bootpeer = self._proxy.getBootstrapPeer(self._prefix).data
except (socket.error, xmlrpclib.Fault), e:
bootpeer = self._registry.getBootstrapPeer(self._prefix)
prefix, address = utils.decrypt(self._key_path, bootpeer).split()
except (socket.error, subprocess.CalledProcessError, ValueError), e:
logging.warning('Failed to bootstrap (%s)', e)
else:
p = subprocess.Popen(('openssl', 'rsautl', '-decrypt', '-inkey', self._key_path),
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
bootpeer = p.communicate(bootpeer)[0].split()
if bootpeer[0] != self._prefix:
self.addPeer(*bootpeer)
return bootpeer
if prefix != self._prefix:
self.addPeer(prefix, address)
return prefix, 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)
with self._db:
q = self._db.execute
try:
(a,), = q("SELECT address FROM peer WHERE prefix=?", (prefix,))
a = a != address if force else \
set(a.split(';')) != set(address.split(';'))
if set_preferred:
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:
q("DELETE FROM peer WHERE prefix IN (SELECT peer"
" FROM volatile.stat ORDER BY try, RANDOM() LIMIT ?,-1)",
(self._db_size,))
a = True
a = q("SELECT peer FROM volatile.stat ORDER BY try, RANDOM()"
" LIMIT ?,-1", (self._db_size,)).fetchall()
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 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):
class Connection(object):
def __init__(self, address, iface, prefix):
self.address_list = list(utils.parse_address(address))
def __init__(self, address_list, iface, prefix):
self.address_list = address_list
self.iface = iface
self.routes = 0
self._prefix = prefix
......@@ -78,8 +78,7 @@ class Connection(object):
except TypeError:
i = len(self.address_list) - 1
if i:
db.addPeer(self._prefix, utils.dump_address(
self.address_list[i:] + self.address_list[:i]), True)
db.addPeer(self._prefix, ','.join(self.address_list[i]), True)
else:
db.connecting(self._prefix, 0)
......@@ -106,7 +105,7 @@ class TunnelManager(object):
def __init__(self, write_pipe, peer_db, openvpn_args, timeout,
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._peer_db = peer_db
self._connecting = set()
......@@ -125,6 +124,7 @@ class TunnelManager(object):
self._encrypt = encrypt
self._gateway_manager = MultiGatewayManager(remote_gateway) \
if remote_gateway else None
self._disable_proto = disable_proto
self._served = set()
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
......@@ -216,6 +216,11 @@ class TunnelManager(object):
if prefix in self._served or prefix in self._connection_dict:
return False
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',
int(prefix, 2), len(prefix))
iface = self.getFreeInterface(prefix)
......@@ -224,7 +229,6 @@ class TunnelManager(object):
for ip in c:
self._gateway_manager.add(ip, True)
c.open(self._write_pipe, self._timeout, self._encrypt, self._ovpn_args)
self._peer_db.connecting(prefix, 1)
return True
def _makeNewTunnels(self, route_counted):
......@@ -435,10 +439,13 @@ class TunnelManager(object):
return
code = ord(msg[0])
if code == 1: # answer
# TODO: do not fail if message contains garbage
# We parse the message in a way to discard a truncated line.
for peer in msg[1:].split('\n')[:-1]:
try:
prefix, address = peer.split()
int(prefix, 2)
except ValueError:
break
if prefix != self._prefix:
self._peer_db.addPeer(prefix, address)
try:
......
import argparse, errno, logging, os, shlex, signal, socket
import struct, subprocess, textwrap, threading, time
from OpenSSL import crypto
import argparse, calendar, errno, logging, os, shlex, signal, socket
import struct, subprocess, sys, textwrap, threading, time, traceback
logging_levels = logging.WARNING, logging.INFO, logging.DEBUG, 5
......@@ -45,6 +44,10 @@ def setupLog(log_level, filename=None, **kw):
logging.addLevelName(5, 'TRACE')
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):
......@@ -128,13 +131,14 @@ def ipFromBin(ip, suffix=''):
struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2)))
def networkFromCa(ca):
ca = crypto.load_certificate(crypto.FILETYPE_PEM, ca)
return bin(ca.get_serial_number())[3:]
def subnetFromCert(cert):
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
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):
return ';'.join(map(','.join, address))
......@@ -150,3 +154,27 @@ def parse_address(address_list):
def binFromSubnet(subnet):
p, l = subnet.split('/')
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
import atexit, errno, logging, os, select, signal
import sqlite3, subprocess, sys, time, traceback
import sqlite3, subprocess, sys, time, threading
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():
parser = utils.ArgParser(fromfile_prefix_chars='@',
......@@ -61,12 +65,14 @@ def getConfig():
" hello interval for Babel to re-establish connection with a"
" node for which the direct connection has been cut.")
_('--table', type=int, default=42,
help="Use given table id. Set 0 to use the main table, if:\n"
"- you are a gateway of this network (the default route will be"
" exported)\n"
"- or you want to use the default route of this network for all"
" communications (in this case, make sure you don't already have"
" a default route).\n")
help="Use given table id. Set 0 to use the main table, if you want to"
" access internet via this network (in this case, make sure you"
" don't already have a default route). Don't use this option with"
" --gateway (main table is automatically used).")
_('--gateway', action='store_true',
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
_('-O', dest='openvpn_args', metavar='ARG', action='append', default=[],
help="Extra arguments to forward to both server and client OpenVPN"
......@@ -101,6 +107,8 @@ def getConfig():
_('--remote-gateway', action='append', dest='gw_list',
help="Force each tunnel to be created through one the given gateways,"
" 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[;...]',
help="Do not run any OpenVPN server, but only 1 OpenVPN client,"
" with specified remotes. Any other option not required in this"
......@@ -108,14 +116,43 @@ def getConfig():
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():
# Get arguments
config = getConfig()
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:
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 += (
'--ca', config.ca,
'--cert', config.cert,
......@@ -136,7 +173,16 @@ def main():
plib.ovpn_log = config.log
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:
config.max_clients = config.client_count * 2
......@@ -220,21 +266,23 @@ def main():
# Init db and tunnels
tunnel_interfaces = server_tunnels.keys()
timeout = 4 * config.hello
cleanup = []
if config.client_count and not config.client:
required('registry')
# Create and open read_only pipe to get server events
r_pipe, write_pipe = os.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,
config.openvpn_args, timeout, config.tunnel_refresh,
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
else:
tunnel_manager = write_pipe = None
cleanup = []
try:
# Source address selection is defined by RFC 6724, and in most
# applications, it usually works thanks to rule 5 (prefer outgoing
......@@ -277,7 +325,9 @@ def main():
cleanup.append(lambda: subprocess.call(if_rt))
x = [my_network]
if config.table:
if config.gateway:
config.table = 0
elif config.table:
x += 'table', str(config.table)
try:
ip('rule', 'from', *x)
......@@ -292,6 +342,26 @@ def main():
if_rt += x[1:]
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
# all drivers here
if sys.platform == 'cygwin':
......@@ -314,7 +384,8 @@ def main():
# main loop
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
while True:
next = tunnel_manager.next_refresh
......@@ -334,6 +405,8 @@ def main():
t = time.time()
if t >= tunnel_manager.next_refresh:
tunnel_manager.refresh()
if t >= next_renew:
raise ReexecException("Restart to renew certificate")
if forwarder and t >= forwarder.next_refresh:
forwarder.refresh()
finally:
......@@ -345,16 +418,17 @@ def main():
except sqlite3.Error:
logging.exception("Restarting with empty cache")
os.rename(db_path, db_path + '.bak')
try:
sys.exitfunc()
finally:
os.execvp(sys.argv[0], sys.argv)
except ReexecException, e:
logging.info(e)
except KeyboardInterrupt:
return 0
except Exception:
f = traceback.format_exception(*sys.exc_info())
logging.error('%s%s', f.pop(), ''.join(f))
utils.log_exception()
sys.exit(1)
try:
sys.exitfunc()
finally:
os.execvp(sys.argv[0], sys.argv)
if __name__ == "__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