Commit a85c8c02 authored by Julien Muchembled's avatar Julien Muchembled

Change protocol to discover addresses of peers to connect to

The previous broadcast model is replaced by a query-response one.
During normal operation, the cache of peers is not used anymore to select
peers to connect to. It now only used for bootstrapping and avoid querying
an already known address.
parent 09ec1327
...@@ -12,19 +12,3 @@ ...@@ -12,19 +12,3 @@
the --private option the --private option
- Put a section about how to build the package from the sources in the README - Put a section about how to build the package from the sources in the README
- review protocol to discover pair:
A node that that wants to establish a new tunnel just have to look at the
routing table and pick a random destination node, actually its re6nst subnet.
If its address is unknown (not in local cache), re6st queries it by sending a
UDP packet to the subnet (anycast, i.e. IP suffix is 0): the reply is also an
UDP packet containing the address string to establish a tunnel. Then, the
tunnel can be established.
For bootstrapping, no destination is known so one has to query the registry
as already implemented. The way the registry discovers node addresses should
be changed so that nodes don't need to declare themselves anymore.
The registry should look at the local peer DB cache.
This new protocol should produce less network trafic than the current one.
...@@ -3,7 +3,7 @@ import math, nemu, os, signal, socket, subprocess, sys, time ...@@ -3,7 +3,7 @@ import math, nemu, os, signal, socket, subprocess, sys, time
from collections import defaultdict from collections import defaultdict
IPTABLES = 'iptables' IPTABLES = 'iptables'
SCREEN = 'screen' SCREEN = 'screen'
VERBOSE = 3 VERBOSE = 4
# registry # registry
# |.2 # |.2
...@@ -132,16 +132,17 @@ for ip in '10.1.1.2', '10.1.1.3', '10.2.1.2', '10.2.1.3': ...@@ -132,16 +132,17 @@ for ip in '10.1.1.2', '10.1.1.3', '10.2.1.2', '10.2.1.3':
else: else:
print "Connectivity IPv4 OK!" print "Connectivity IPv4 OK!"
registry.screen('../re6stnet @registry/re6stnet.conf --ip 10.0.0.2 -v%u' % VERBOSE,
'../re6st-registry @registry/re6st-registry.conf')
gateway1.screen('miniupnpd -d -f miniupnpd.conf -P miniupnpd.pid -a 10.1.1.1' 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)
machine1.screen('../re6stnet @m1/re6stnet.conf -v%u' % VERBOSE) if 1:
machine2.screen('../re6stnet @m2/re6stnet.conf -v%u' % VERBOSE) registry.screen('../re6stnet @registry/re6stnet.conf --ip 10.0.0.2 -v%u' % VERBOSE,
machine3.screen('../re6stnet @m3/re6stnet.conf -v%u -i%s' % (VERBOSE, m3_if_0.name)) '../re6st-registry @registry/re6st-registry.conf')
machine4.screen('../re6stnet @m4/re6stnet.conf -v%u -i%s' % (VERBOSE, m4_if_0.name)) machine1.screen('../re6stnet @m1/re6stnet.conf -v%u' % VERBOSE)
machine5.screen('../re6stnet @m5/re6stnet.conf -v%u' % VERBOSE) machine2.screen('../re6stnet @m2/re6stnet.conf -v%u' % VERBOSE)
machine6.screen('../re6stnet @m6/re6stnet.conf -v%u' % VERBOSE) machine3.screen('../re6stnet @m3/re6stnet.conf -v%u -i%s' % (VERBOSE, m3_if_0.name))
machine4.screen('../re6stnet @m4/re6stnet.conf -v%u -i%s' % (VERBOSE, m4_if_0.name))
machine5.screen('../re6stnet @m5/re6stnet.conf -v%u' % VERBOSE)
machine6.screen('../re6stnet @m6/re6stnet.conf -v%u' % VERBOSE)
nodes = registry, machine1, machine2, machine3, machine4, machine5, machine6 nodes = registry, machine1, machine2, machine3, machine4, machine5, machine6
_ll = {} _ll = {}
......
PRAGMA foreign_keys=OFF; PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION; BEGIN TRANSACTION;
CREATE TABLE peers (
prefix text primary key not null,
address text not null,
date integer default (strftime('%s','now')));
CREATE TABLE token ( CREATE TABLE token (
token text primary key not null, token text primary key not null,
email text not null, email text not null,
...@@ -190,5 +186,4 @@ rlyT1Q== ...@@ -190,5 +186,4 @@ rlyT1Q==
INSERT INTO "cert" VALUES('0000000000001001',NULL,NULL); INSERT INTO "cert" VALUES('0000000000001001',NULL,NULL);
INSERT INTO "cert" VALUES('000000000000101',NULL,NULL); INSERT INTO "cert" VALUES('000000000000101',NULL,NULL);
INSERT INTO "cert" VALUES('00000000000011',NULL,NULL); INSERT INTO "cert" VALUES('00000000000011',NULL,NULL);
CREATE INDEX peers_ping ON peers(date);
COMMIT; COMMIT;
...@@ -4,7 +4,7 @@ import subprocess, time, threading, traceback, errno, logging, os, xmlrpclib ...@@ -4,7 +4,7 @@ import subprocess, time, threading, traceback, errno, logging, os, xmlrpclib
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
from email.mime.text import MIMEText from email.mime.text import MIMEText
from OpenSSL import crypto from OpenSSL import crypto
from re6st import utils 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 365 -out ca.crt
...@@ -64,18 +64,15 @@ class main(object): ...@@ -64,18 +64,15 @@ class main(object):
help='VPN IP of the node on which runs the registry') help='VPN IP of the node on which runs the registry')
self.config = parser.parse_args() self.config = parser.parse_args()
if not self.config.private: if self.config.private:
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
else:
logging.warning('You have declared no private address' logging.warning('You have declared no private address'
', either this is the first start, or you should' ', either this is the first start, or you should'
'check you configuration') 'check you configuration')
# Database initializing # Database initializing
self.db = sqlite3.connect(self.config.db, isolation_level=None) self.db = sqlite3.connect(self.config.db, isolation_level=None)
self.db.execute("""CREATE TABLE IF NOT EXISTS peers (
prefix text primary key not null,
address text not null,
date integer default (strftime('%s','now')))""")
self.db.execute("CREATE INDEX IF NOT EXISTS peers_ping ON peers(date)")
self.db.execute("""CREATE TABLE IF NOT EXISTS token ( self.db.execute("""CREATE TABLE IF NOT EXISTS token (
token text primary key not null, token text primary key not null,
email text not null, email text not null,
...@@ -196,40 +193,35 @@ class main(object): ...@@ -196,40 +193,35 @@ class main(object):
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.ca) return crypto.dump_certificate(crypto.FILETYPE_PEM, self.ca)
def getPrivateAddress(self, handler): def getPrivateAddress(self, handler):
return 'http://[%s]:%u' % (self.config.private, self.config.port) return self.config.private
def getBootstrapPeer(self, handler, client_prefix): def getBootstrapPeer(self, handler, client_prefix):
cert, = self.db.execute("SELECT cert FROM cert WHERE prefix = ?", cert, = self.db.execute("SELECT cert FROM cert WHERE prefix = ?",
(client_prefix,)).next() (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: try:
prefix, address = self.db.execute("""SELECT prefix, address FROM peers peer = msg[1:].split('\n')[-2]
WHERE prefix != ? ORDER BY random() LIMIT 1""", (client_prefix,)).next() except IndexError:
except StopIteration: peer = ''
logging.info('No peer to send for bootstrap') if peer is None:
raise 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() r, w = os.pipe()
try: try:
threading.Thread(target=os.write, args=(w, cert)).start() threading.Thread(target=os.write, args=(w, cert)).start()
p = subprocess.Popen(('openssl', 'rsautl', '-encrypt', '-certin', '-inkey', '/proc/self/fd/%u' % r), p = subprocess.Popen(('openssl', 'rsautl', '-encrypt', '-certin', '-inkey', '/proc/self/fd/%u' % r),
stdin=subprocess.PIPE, stdout=subprocess.PIPE) stdin=subprocess.PIPE, stdout=subprocess.PIPE)
logging.info("Sending bootstrap peer (%s, %s)" % (prefix, address)) return xmlrpclib.Binary(p.communicate(peer)[0])
return xmlrpclib.Binary(p.communicate('%s %s' % (prefix, address))[0])
finally: finally:
os.close(r) os.close(r)
os.close(w) os.close(w)
def declare(self, handler, address):
client_address, _, _, _ = handler.client_address
client_ip = utils.binFromIp(client_address)
if client_ip.startswith(self.network):
prefix = client_ip[len(self.network):]
prefix, = self.db.execute("SELECT prefix FROM cert WHERE prefix <= ? ORDER BY prefix DESC LIMIT 1", (prefix,)).next()
self.db.execute("INSERT OR REPLACE INTO peers (prefix, address) VALUES (?,?)", (prefix, address))
return True
else:
logging.warning("Unauthorized connection from %s which does not start with %s"
% (utils.ipFromBin(client_ip), utils.ipFromBin(self.network.ljust(128, '0'))))
return False
if __name__ == "__main__": if __name__ == "__main__":
main() main()
This diff is collapsed.
...@@ -5,4 +5,4 @@ if os.environ['script_type'] == 'up': ...@@ -5,4 +5,4 @@ if os.environ['script_type'] == 'up':
os.execlp('ip', 'ip', 'link', 'set', os.environ['dev'], 'up') os.execlp('ip', 'ip', 'link', 'set', os.environ['dev'], 'up')
# Write into pipe external ip address received # Write into pipe external ip address received
os.write(int(sys.argv[1]), '%(script_type)s %(OPENVPN_external_ip)s\n' % os.environ) os.write(int(sys.argv[1]), '%(script_type)s %(common_name)s %(OPENVPN_external_ip)s\n' % os.environ)
...@@ -74,7 +74,7 @@ def router(network, subnet, subnet_size, interface_list, ...@@ -74,7 +74,7 @@ def router(network, subnet, subnet_size, interface_list,
'-C', 'out local ip %s/%s le %s' % (subnet, subnet_size, subnet_size), '-C', 'out local ip %s/%s le %s' % (subnet, subnet_size, subnet_size),
'-C', 'out local deny', '-C', 'out local deny',
# Route VIFIB ip adresses # Route VIFIB ip adresses
'-C', 'in ip %s::/%u' % (utils.ipFromBin(network), len(network)), '-C', 'in ip %s/%u' % (utils.ipFromBin(network), len(network)),
# Route only addresse in the 'local' network, # Route only addresse in the 'local' network,
# or other entire networks # or other entire networks
#'-C', 'in ip %s' % (config.internal_ip), #'-C', 'in ip %s' % (config.internal_ip),
......
This diff is collapsed.
...@@ -4,10 +4,13 @@ from OpenSSL import crypto ...@@ -4,10 +4,13 @@ from OpenSSL import crypto
logging_levels = logging.WARNING, logging.INFO, logging.DEBUG, 5 logging_levels = logging.WARNING, logging.INFO, logging.DEBUG, 5
def setupLog(log_level): def setupLog(log_level, **kw):
logging.basicConfig(level=logging_levels[log_level], if log_level:
logging.basicConfig(level=logging_levels[log_level-1],
format='%(asctime)s %(levelname)-9s %(message)s', format='%(asctime)s %(levelname)-9s %(message)s',
datefmt='%d-%m-%Y %H:%M:%S') datefmt='%d-%m-%Y %H:%M:%S', **kw)
else:
logging.disable(logging.CRITICAL)
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)
...@@ -28,19 +31,10 @@ def binFromIp(ip): ...@@ -28,19 +31,10 @@ def binFromIp(ip):
return bin(ip1)[2:].rjust(64, '0') + bin(ip2)[2:].rjust(64, '0') return bin(ip1)[2:].rjust(64, '0') + bin(ip2)[2:].rjust(64, '0')
def ipFromBin(prefix): def ipFromBin(prefix, suffix=''):
prefix = hex(int(prefix, 2))[2:] ip = prefix + suffix.rjust(128 - len(prefix), '0')
ip = '' return socket.inet_ntop(socket.AF_INET6,
for i in xrange(0, len(prefix) - 1, 4): struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2)))
ip += prefix[i:i + 4] + ':'
return ip.rstrip(':')
def ipFromPrefix(re6stnet, prefix, prefix_len):
prefix = bin(int(prefix))[2:].rjust(prefix_len, '0')
ip_t = (re6stnet + prefix).ljust(127, '0').ljust(128, '1')
return ipFromBin(ip_t), prefix
def networkFromCa(ca_path): def networkFromCa(ca_path):
# Get network prefix from ca.crt # Get network prefix from ca.crt
...@@ -48,15 +42,14 @@ def networkFromCa(ca_path): ...@@ -48,15 +42,14 @@ def networkFromCa(ca_path):
ca = crypto.load_certificate(crypto.FILETYPE_PEM, f.read()) ca = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
return bin(ca.get_serial_number())[3:] return bin(ca.get_serial_number())[3:]
def ipFromCert(network, cert_path): def ipFromCert(network, cert_path):
# Get ip from cert.crt # Get ip from cert.crt
with open(cert_path, 'r') as f: with open(cert_path, 'r') as f:
cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read()) cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
subject = cert.get_subject() subject = cert.get_subject()
prefix, prefix_len = subject.CN.split('/') prefix, prefix_len = subject.CN.split('/')
return ipFromPrefix(network, prefix, int(prefix_len)) prefix = bin(int(prefix))[2:].rjust(int(prefix_len), '0')
return ipFromBin(network + prefix, '1'), prefix
def address_str(address): def address_str(address):
return ';'.join(map(','.join, address)) return ';'.join(map(','.join, address))
...@@ -68,7 +61,5 @@ def address_list(address_list): ...@@ -68,7 +61,5 @@ def address_list(address_list):
def binFromSubnet(subnet): def binFromSubnet(subnet):
prefix, subnet_size = subnet.split('/') p, l = subnet.split('/')
binary = bin(int(prefix))[2:] return bin(int(p))[2:].rjust(int(l), '0')
binary = ('0' * (int(subnet_size) - len(binary))) + binary
return binary
#!/usr/bin/env python #!/usr/bin/env python
import atexit, os, sys, select, socket, time import atexit, os, sys, select, time
import argparse, signal, subprocess, sqlite3, logging, traceback import argparse, signal, subprocess, sqlite3, logging, traceback
from re6st import plib, utils, db, tunnel from re6st import plib, utils, db, tunnel
...@@ -27,13 +27,11 @@ def getConfig(): ...@@ -27,13 +27,11 @@ def getConfig():
_('--registry', required=True, _('--registry', required=True,
help="HTTP URL of the discovery peer server," help="HTTP URL of the discovery peer server,"
" with public host (default port: 80)") " with public host (default port: 80)")
_('--peers-db-refresh', default=43200, type=int,
help='the time (seconds) to wait before refreshing the peers db')
_('-l', '--log', default='/var/log', _('-l', '--log', default='/var/log',
help='Path to re6stnet logs directory') help='Path to re6stnet logs directory')
_('-s', '--state', default='/var/lib/re6stnet', _('-s', '--state', default='/var/lib/re6stnet',
help='Path to re6stnet state directory') help='Path to re6stnet state directory')
_('-v', '--verbose', default=0, type=int, _('-v', '--verbose', default=1, type=int,
help='Log level of re6st itself') help='Log level of re6st itself')
_('-i', '--interface', action='append', dest='iface_list', default=[], _('-i', '--interface', action='append', dest='iface_list', default=[],
help='Extra interface for LAN discovery') help='Extra interface for LAN discovery')
...@@ -83,7 +81,8 @@ def main(): ...@@ -83,7 +81,8 @@ def main():
db_path = os.path.join(config.state, 'peers.db') db_path = os.path.join(config.state, 'peers.db')
# Set logging # Set logging
utils.setupLog(config.verbose) utils.setupLog(config.verbose,
filename=os.path.join(config.log, 're6stnet.log'))
logging.trace("Configuration :\n%s" % config) logging.trace("Configuration :\n%s" % config)
...@@ -124,13 +123,11 @@ def main(): ...@@ -124,13 +123,11 @@ def main():
if address: if address:
ip_changed = None ip_changed = None
peer_db = db.PeerManager(db_path, config.registry, config.key, peer_db = db.PeerDB(db_path, config.registry, config.key, prefix)
config.peers_db_refresh, address, internal_ip, prefix,
ip_changed, 200)
tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args, tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db, openvpn_args,
config.hello, config.tunnel_refresh, config.connection_count, config.hello, config.tunnel_refresh, config.connection_count,
config.iface_list, network, prefix, 2, config.encrypt) config.iface_list, network, prefix, address, ip_changed,
peer_db.tunnel_manager = tunnel_manager config.encrypt)
# Launch routing protocol. WARNING : you have to be root to start babeld # Launch routing protocol. WARNING : you have to be root to start babeld
server_tunnels = {} server_tunnels = {}
...@@ -156,24 +153,24 @@ def main(): ...@@ -156,24 +153,24 @@ def main():
config.connection_count, config.dh, write_pipe, port, config.connection_count, config.dh, write_pipe, port,
proto, config.hello, config.encrypt, *openvpn_args)) proto, config.hello, config.encrypt, *openvpn_args))
while True: while True:
nextUpdate = min(tunnel_manager.next_refresh, peer_db.next_refresh) next = tunnel_manager.next_refresh
if forwarder: if forwarder:
nextUpdate = min(nextUpdate, forwarder.next_refresh) next = min(next, forwarder.next_refresh)
nextUpdate = max(0, nextUpdate - time.time()) r = [read_pipe, tunnel_manager.sock]
select_list = [read_pipe] r = select.select(r, [], [], max(0, next - time.time()))[0]
if peer_db.socket_file: if read_pipe in r:
select_list.append(peer_db.socket_file) tunnel_manager.handleTunnelEvent(read_pipe.readline())
ready, tmp1, tmp2 = select.select(select_list, [], [], nextUpdate) if tunnel_manager.sock in r:
if read_pipe in ready: tunnel_manager.handlePeerEvent()
peer_db.handle_message(read_pipe.readline()) t = time.time()
if time.time() >= peer_db.next_refresh: if t >= tunnel_manager.next_refresh:
peer_db.refresh()
if time.time() >= tunnel_manager.next_refresh:
tunnel_manager.refresh() tunnel_manager.refresh()
if forwarder and time.time() > forwarder.next_refresh: if forwarder and t >= forwarder.next_refresh:
forwarder.refresh() forwarder.refresh()
if peer_db.socket_file in ready: except Exception:
peer_db.readSocket() f = traceback.format_exception(*sys.exc_info())
logging.error('%s%s', f.pop(), ''.join(f))
raise
finally: finally:
router.terminate() router.terminate()
for p in server_process: for p in server_process:
...@@ -186,7 +183,6 @@ def main(): ...@@ -186,7 +183,6 @@ def main():
except: except:
pass pass
except sqlite3.Error: except sqlite3.Error:
traceback.print_exc()
os.rename(db_path, db_path + '.bak') os.rename(db_path, db_path + '.bak')
try: try:
sys.exitfunc() sys.exitfunc()
......
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