Commit acec3e4d authored by Joanne Hugé's avatar Joanne Hugé

Add communities to re6st

parent 72d65119
# Community example config
000 *
001 @AS AU
010 FR DE IT
......@@ -7,3 +7,4 @@ hello 4
client-count 2
tunnel-refresh 100
ipv4 10.42.0.0/16 8
community registry/community.conf
......@@ -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
be able to connect to it.
You can use communities to group prefixes in different subprefixes based on
their location in. 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 continent (Alpha-2
code) preceded by "@". See demo/registry/community.conf for an example.
TROUBLESHOOTING
===============
......
......@@ -45,6 +45,10 @@ def main():
_('--anonymous', action='store_true',
help="Request an anonymous certificate. No email is required but the"
" 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()
if config.dir:
os.chdir(config.dir)
......@@ -141,7 +145,10 @@ def main():
# to avoid using our token for nothing.
cert_fd = os.open(cert_path, os.O_CREAT | os.O_WRONLY, 0666)
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:
token_advice = None
sys.exit("Error: invalid or expired token")
......
......@@ -113,6 +113,8 @@ def main():
help="Reject nodes that are too old. Current is %s." % version.protocol)
_('--authorized-origin', action='append', default=['127.0.0.1', '::1'],
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
_('--hello', type=int, default=15,
......
......@@ -68,6 +68,22 @@ class RegistryServer(object):
self.sessions = {}
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
# Parse community file
self.community_map = {}
if config.community:
has_default = False
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[0]] = x[1:]
has_default = True if '*' in x[1:] else has_default
if not has_default:
sys.exit("Invalid community configuration: default location ('*') missing")
if not self.community_map:
self.community_map[''] = '*'
# Database initializing
db_dir = os.path.dirname(self.config.db)
db_dir and utils.makedirs(db_dir)
......@@ -90,6 +106,21 @@ class RegistryServer(object):
"cert TEXT")
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",
"serial INTEGER PRIMARY KEY NOT NULL",
# Expiration date of revoked certificate.
......@@ -113,9 +144,10 @@ class RegistryServer(object):
country = database.Reader(db).country
def geoiplookup(ip):
try:
return country(ip).country.iso_code.encode()
except errors.AddressNotFoundError:
return
req = country(ip)
return req.country.iso_code.encode(), req.continent.code.encode()
except (errors.AddressNotFoundError, ValueError):
return '*', '*'
self._geoiplookup = geoiplookup
elif self.config.same_country:
sys.exit("Can not respect 'same_country' network configuration"
......@@ -279,6 +311,9 @@ class RegistryServer(object):
request.headers.get("X-Forwarded-For") or
request.headers.get("host"),
request.headers.get("user-agent"))
if 'ip' in kw and not kw['ip']:
kw['ip'] = request.headers.get("X-Forwarded-For") or request.headers.get("host")
kw['ip'] = kw['ip'].split(',')[0].strip()
try:
result = m(**kw)
except HTTPError, e:
......@@ -385,6 +420,18 @@ class RegistryServer(object):
s.sendmail(self.email, email, msg.as_string())
s.quit()
def getCommunity(self, country, continent):
default = ''
for prefix in self.community_map:
if country in self.community_map[prefix]:
return prefix
for prefix in self.community_map:
if continent in self.community_map[prefix]:
return prefix
if '*' in self.community_map[prefix]:
default = prefix
return default
def mergePrefixes(self):
q = self.db.execute
prev_prefix = None
......@@ -407,31 +454,46 @@ class RegistryServer(object):
else:
prev_prefix = prefix
def newPrefix(self, prefix_len):
def newPrefix(self, prefix_len, community):
community_len = len(community)
prefix_len += community_len
max_len = 128 - len(self.network)
assert 0 < prefix_len <= max_len
q = self.db.execute
while True:
try:
# Find longest free prefix whithin community.
prefix, = q(
"SELECT prefix FROM cert"
" WHERE length(prefix) <= ? AND cert is null"
" WHERE prefix LIKE ?"
" AND length(prefix) <= ? AND cert is null"
" ORDER BY length(prefix) DESC",
(prefix_len,)).next()
(community + '%', prefix_len)).next()
except StopIteration:
logging.error('No more free /%u prefix available', prefix_len)
raise
while len(prefix) < prefix_len:
# Community not yet allocated?
# There should be exactly 1 row whose
# 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 + '1', prefix))
prefix += '0'
(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:
if len(prefix) < max_len or '1' in prefix[community_len:]:
return prefix
q("UPDATE cert SET cert = 'reserved' WHERE prefix = ?", (prefix,))
@rpc
def requestCertificate(self, token, req):
def requestCertificate(self, token, req, location='', ip=''):
req = crypto.load_certificate_request(crypto.FILETYPE_PEM, req)
with self.lock:
with self.db:
......@@ -451,7 +513,11 @@ class RegistryServer(object):
if not prefix_len:
raise HTTPError(httplib.FORBIDDEN)
email = None
prefix = self.newPrefix(prefix_len)
country, continent = '*', '*'
if os.getenv('GEOIP2_MMDB'):
country, continent = location.split(',') if location else self._geoiplookup(ip)
continent = '@%s' % continent if continent != '*' else '*'
prefix = self.newPrefix(prefix_len, self.getCommunity(country, continent))
self.db.execute("UPDATE cert SET email = ? WHERE prefix = ?",
(email, prefix))
if self.prefix is None:
......@@ -550,7 +616,8 @@ class RegistryServer(object):
@rpc
def getCountry(self, cn, address):
return self._geoiplookup(address)
country = self._geoiplookup(address)[0]
return None if country == '*' else country
@rpc
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