Commit badb9a77 authored by Thomas Gambier's avatar Thomas Gambier 🚴🏼
parents 99cf2adc fdc712ee
...@@ -277,7 +277,7 @@ def new_network(registry, reg_addr, serial, ca): ...@@ -277,7 +277,7 @@ def new_network(registry, reg_addr, serial, ca):
with new_network(registry, REGISTRY, REGISTRY_SERIAL, 'ca.crt') as new_node: with new_network(registry, REGISTRY, REGISTRY_SERIAL, 'ca.crt') as new_node:
new_node(machine1, 'm1', '-I%s' % m1_if_0.name) new_node(machine1, 'm1', '-I%s' % m1_if_0.name)
new_node(machine2, 'm2', '--remote-gateway 10.1.1.1', prefix_len=80) new_node(machine2, 'm2', '--remote-gateway 10.1.1.1', prefix_len=77)
new_node(machine3, 'm3', '-i%s' % m3_if_0.name) new_node(machine3, 'm3', '-i%s' % m3_if_0.name)
new_node(machine4, 'm4', '-i%s' % m4_if_0.name) new_node(machine4, 'm4', '-i%s' % m4_if_0.name)
new_node(machine5, 'm5', '-i%s' % m5_if_0.name) new_node(machine5, 'm5', '-i%s' % m5_if_0.name)
......
# Community example config
000 *
001 @AS AU
010 FR DE IT
...@@ -7,3 +7,5 @@ hello 4 ...@@ -7,3 +7,5 @@ hello 4
client-count 2 client-count 2
tunnel-refresh 100 tunnel-refresh 100
ipv4 10.42.0.0/16 8 ipv4 10.42.0.0/16 8
prefix-length 13
community registry/community.conf
...@@ -167,6 +167,12 @@ For bootstrapping, you may have to explicitly set an IP in the configuration ...@@ -167,6 +167,12 @@ For bootstrapping, you may have to explicitly set an IP in the configuration
of the first node, via the ``--ip`` option. Otherwise, additional nodes won't of the first node, via the ``--ip`` option. Otherwise, additional nodes won't
be able to connect to it. be able to connect to it.
You can use communities to group prefixes in different subprefixes based on
their location. Each line in the community configuration is a mapping
from a subprefix (in binary) to a list of locations. Each location is either
"*" (default assignment), a country (Alpha-2 code), or a continent (Alpha-2
code) preceded by "@". See demo/registry/community.conf for an example.
TROUBLESHOOTING TROUBLESHOOTING
=============== ===============
......
...@@ -45,6 +45,10 @@ def main(): ...@@ -45,6 +45,10 @@ def main():
_('--anonymous', action='store_true', _('--anonymous', action='store_true',
help="Request an anonymous certificate. No email is required but the" help="Request an anonymous certificate. No email is required but the"
" registry may deliver a longer prefix.") " registry may deliver a longer prefix.")
_('--location',
help="Alpha-2 codes of country and continent separated by a comma."
" Will be used for the community assignment (default: location"
" is automatically detected). Example: FR,EU")
config = parser.parse_args() config = parser.parse_args()
if config.dir: if config.dir:
os.chdir(config.dir) os.chdir(config.dir)
...@@ -141,7 +145,10 @@ def main(): ...@@ -141,7 +145,10 @@ def main():
# to avoid using our token for nothing. # to avoid using our token for nothing.
cert_fd = os.open(cert_path, os.O_CREAT | os.O_WRONLY, 0666) cert_fd = os.open(cert_path, os.O_CREAT | os.O_WRONLY, 0666)
print "Requesting certificate ..." print "Requesting certificate ..."
cert = s.requestCertificate(token, req) if config.location:
cert = s.requestCertificate(token, req, location=config.location)
else:
cert = s.requestCertificate(token, req)
if not cert: if not cert:
token_advice = None token_advice = None
sys.exit("Error: invalid or expired token") sys.exit("Error: invalid or expired token")
......
...@@ -110,6 +110,9 @@ def main(): ...@@ -110,6 +110,9 @@ def main():
help="Reject nodes that are too old. Current is %s." % version.protocol) help="Reject nodes that are too old. Current is %s." % version.protocol)
_('--authorized-origin', action='append', default=['127.0.0.1', '::1'], _('--authorized-origin', action='append', default=['127.0.0.1', '::1'],
help="Authorized IPs to access origin-restricted RPC.") help="Authorized IPs to access origin-restricted RPC.")
_('--community',
help="File containing community configuration. This file cannot be"
" empty and must contain the default location ('*').")
_ = parser.add_argument_group('routing').add_argument _ = parser.add_argument_group('routing').add_argument
_('--hello', type=int, default=15, _('--hello', type=int, default=15,
......
...@@ -68,6 +68,20 @@ class RegistryServer(object): ...@@ -68,6 +68,20 @@ class RegistryServer(object):
self.sessions = {} self.sessions = {}
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
# Parse community file
self.community_map = {}
if config.community:
with open(config.community) as x:
for x in x:
x = x.strip()
if x and not x.startswith('#'):
x = x.split()
self.community_map[x.pop(0)] = x
if sum('*' in x for x in self.community_map.itervalues()) != 1:
sys.exit("Invalid community configuration: missing or multiple default location ('*')")
else:
self.community_map[''] = '*'
# Database initializing # Database initializing
db_dir = os.path.dirname(self.config.db) db_dir = os.path.dirname(self.config.db)
db_dir and utils.makedirs(db_dir) db_dir and utils.makedirs(db_dir)
...@@ -88,7 +102,23 @@ class RegistryServer(object): ...@@ -88,7 +102,23 @@ class RegistryServer(object):
"prefix TEXT PRIMARY KEY NOT NULL", "prefix TEXT PRIMARY KEY NOT NULL",
"email TEXT", "email TEXT",
"cert TEXT") "cert TEXT")
self.db.execute("INSERT OR IGNORE INTO cert VALUES ('',null,null)") if not self.db.execute("SELECT 1 FROM cert LIMIT 1").fetchone():
self.db.execute("INSERT INTO cert VALUES ('',null,null)")
prev = '-'
for community in sorted(self.community_map):
if community.startswith(prev):
err = "communities %s and %s overlap" % (prev, community)
else:
x = self.db.execute("SELECT prefix, cert FROM cert"
" WHERE substr(?,1,length(prefix)) = prefix",
(community,)).fetchone()
if not x or x[1] is None:
prev = community
continue
err = "prefix %s contains community %s" % (x[0], community)
sys.exit("Invalid community configuration: " + err)
utils.sqliteCreateTable(self.db, "crl", utils.sqliteCreateTable(self.db, "crl",
"serial INTEGER PRIMARY KEY NOT NULL", "serial INTEGER PRIMARY KEY NOT NULL",
# Expiration date of revoked certificate. # Expiration date of revoked certificate.
...@@ -106,15 +136,16 @@ class RegistryServer(object): ...@@ -106,15 +136,16 @@ class RegistryServer(object):
self.ctl = ctl.Babel(os.path.join(config.run, 'babeld.sock'), self.ctl = ctl.Babel(os.path.join(config.run, 'babeld.sock'),
weakref.proxy(self), self.network) weakref.proxy(self), self.network)
db = os.getenv('GEOIP2_MMDB') self.geoip_db = os.getenv('GEOIP2_MMDB')
if db: if self.geoip_db:
from geoip2 import database, errors from geoip2 import database, errors
country = database.Reader(db).country country = database.Reader(self.geoip_db).country
def geoiplookup(ip): def geoiplookup(ip):
try: try:
return country(ip).country.iso_code.encode() req = country(ip)
except errors.AddressNotFoundError: return req.country.iso_code.encode(), req.continent.code.encode()
return except (errors.AddressNotFoundError, ValueError):
return '*', '*'
self._geoiplookup = geoiplookup self._geoiplookup = geoiplookup
elif self.config.same_country: elif self.config.same_country:
sys.exit("Can not respect 'same_country' network configuration" sys.exit("Can not respect 'same_country' network configuration"
...@@ -246,8 +277,7 @@ class RegistryServer(object): ...@@ -246,8 +277,7 @@ class RegistryServer(object):
(prefix,)) (prefix,))
elif not_after is None or x < not_after: elif not_after is None or x < not_after:
not_after = x not_after = x
# TODO: reduce 'cert' table by merging free slots self.mergePrefixes()
# (IOW, do the contrary of newPrefix)
self.timeout = not_after and not_after + GRACE_PERIOD self.timeout = not_after and not_after + GRACE_PERIOD
def handle_request(self, request, method, kw): def handle_request(self, request, method, kw):
...@@ -279,6 +309,9 @@ class RegistryServer(object): ...@@ -279,6 +309,9 @@ class RegistryServer(object):
request.headers.get("X-Forwarded-For") or request.headers.get("X-Forwarded-For") or
request.headers.get("host"), request.headers.get("host"),
request.headers.get("user-agent")) request.headers.get("user-agent"))
if not kw.get('ip', True):
kw['ip'] = (request.headers.get("X-Forwarded-For", "").split(',',1)[0].strip() or
request.headers.get("host"))
try: try:
result = m(**kw) result = m(**kw)
except HTTPError, e: except HTTPError, e:
...@@ -385,26 +418,80 @@ class RegistryServer(object): ...@@ -385,26 +418,80 @@ class RegistryServer(object):
s.sendmail(self.email, email, msg.as_string()) s.sendmail(self.email, email, msg.as_string())
s.quit() s.quit()
def newPrefix(self, prefix_len): def getCommunity(self, country, continent):
for prefix, location_list in self.community_map.iteritems():
if country in location_list:
return prefix
default = ''
for prefix, location_list in self.community_map.iteritems():
if continent in location_list:
return prefix
if '*' in location_list:
default = prefix
return default
def mergePrefixes(self):
q = self.db.execute
prev_prefix = None
max_len = 128,
while True:
max_len = q("SELECT max(length(prefix)) FROM cert"
" WHERE cert is null AND length(prefix) < ?",
max_len).next()
if not max_len[0]:
break
for prefix, in q("SELECT prefix FROM cert"
" WHERE cert is null AND length(prefix) = ?"
" ORDER BY prefix",
max_len):
if prev_prefix and prefix[:-1] == prev_prefix[:-1]:
q("UPDATE cert SET prefix = ? WHERE prefix = ?",
(prefix[:-1], prev_prefix))
q("DELETE FROM cert WHERE prefix = ?", (prefix,))
prev_prefix = None
else:
prev_prefix = prefix
def newPrefix(self, prefix_len, community):
community_len = len(community)
prefix_len += community_len
max_len = 128 - len(self.network) max_len = 128 - len(self.network)
assert 0 < prefix_len <= max_len assert 0 < prefix_len <= max_len
try: q = self.db.execute
prefix, = self.db.execute("""SELECT prefix FROM cert WHERE length(prefix) <= ? AND cert is null while True:
ORDER BY length(prefix) DESC""", (prefix_len,)).next() try:
except StopIteration: # Find longest free prefix whithin community.
logging.error('No more free /%u prefix available', prefix_len) prefix, = q(
raise "SELECT prefix FROM cert"
while len(prefix) < prefix_len: " WHERE prefix LIKE ?"
self.db.execute("UPDATE cert SET prefix = ? WHERE prefix = ?", (prefix + '1', prefix)) " AND length(prefix) <= ? AND cert is null"
prefix += '0' " ORDER BY length(prefix) DESC",
self.db.execute("INSERT INTO cert VALUES (?,null,null)", (prefix,)) (community + '%', prefix_len)).next()
if len(prefix) < max_len or '1' in prefix: except StopIteration:
return prefix # Community not yet allocated?
self.db.execute("UPDATE cert SET cert = 'reserved' WHERE prefix = ?", (prefix,)) # There should be exactly 1 row whose
return self.newPrefix(prefix_len) # prefix is the beginning of community.
prefix, x = q("SELECT prefix, cert FROM cert"
" WHERE substr(?,1,length(prefix)) = prefix",
(community,)).next()
if x is not None:
logging.error('No more free /%u prefix available',
prefix_len)
raise
# Split the tree until prefix has wanted length.
for x in xrange(len(prefix), prefix_len):
# Prefix starts with community, then we complete with 0.
x = community[x] if x < community_len else '0'
q("UPDATE cert SET prefix = ? WHERE prefix = ?",
(prefix + str(1-int(x)), prefix))
prefix += x
q("INSERT INTO cert VALUES (?,null,null)", (prefix,))
if len(prefix) < max_len or '1' in prefix[community_len:]:
return prefix
q("UPDATE cert SET cert = 'reserved' WHERE prefix = ?", (prefix,))
@rpc @rpc
def requestCertificate(self, token, req): def requestCertificate(self, token, req, location='', ip=''):
req = crypto.load_certificate_request(crypto.FILETYPE_PEM, req) req = crypto.load_certificate_request(crypto.FILETYPE_PEM, req)
with self.lock: with self.lock:
with self.db: with self.db:
...@@ -424,7 +511,12 @@ class RegistryServer(object): ...@@ -424,7 +511,12 @@ class RegistryServer(object):
if not prefix_len: if not prefix_len:
raise HTTPError(httplib.FORBIDDEN) raise HTTPError(httplib.FORBIDDEN)
email = None email = None
prefix = self.newPrefix(prefix_len) country, continent = '*', '*'
if self.geoip_db:
country, continent = location.split(',') if location else self._geoiplookup(ip)
if continent != '*':
continent = '@' + continent
prefix = self.newPrefix(prefix_len, self.getCommunity(country, continent))
self.db.execute("UPDATE cert SET email = ? WHERE prefix = ?", self.db.execute("UPDATE cert SET email = ? WHERE prefix = ?",
(email, prefix)) (email, prefix))
if self.prefix is None: if self.prefix is None:
...@@ -523,7 +615,8 @@ class RegistryServer(object): ...@@ -523,7 +615,8 @@ class RegistryServer(object):
@rpc @rpc
def getCountry(self, cn, address): def getCountry(self, cn, address):
return self._geoiplookup(address) country = self._geoiplookup(address)[0]
return None if country == '*' else country
@rpc @rpc
def getBootstrapPeer(self, cn): def getBootstrapPeer(self, cn):
......
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