Commit 85d77bd8 authored by Joanne Hugé's avatar Joanne Hugé Committed by Julien Muchembled

New --country option; add country in addresses

This commit concerns networks that use the --same-country option.
We recently discovered that the IP geolocation database contains
incorrect entries. To work around this, the protocol needs to be
changed by adding the country as 4th field in addresses (the first 3
are: ip, port, protocol) and the new --country option allows a node
to announce a country that differs from the one the GeoIP DB.

Thanks to the previous commits it's possible to implement backward
compatibility, by not sending the 4th field (country) to nodes that
can't parse it. Of course, these old nodes would continue to not
create appropriate tunnels and after a while, the administrator of
the network may decide to increase registry's --min-protocol (7).

In a network with only nodes that implement this last version of the
protocol, the nodes may only use the GeoIP DB to resolve their own IPs.

See merge request nexedi/re6stnet!27
parent bb7e6376
......@@ -65,6 +65,9 @@ def getConfig():
" patch this process. Use:\n"
" socat - UNIX:<SOCK>\n"
"to access it.")
_('--country', metavar='CODE',
help="Country code that is advertised to other nodes"
"(default: country is fetched from MaxMind database)")
_ = parser.add_argument_group('routing').add_argument
_('-B', dest='babel_args', metavar='ARG', action='append', default=[],
......@@ -294,12 +297,12 @@ def main():
if config.client_count and not config.client:
tunnel_manager = tunnel.TunnelManager(control_socket,
cache, cert, config.openvpn_args, timeout,
config.client_count, config.iface_list, address, ip_changed,
config.client_count, config.iface_list, config.country, address, ip_changed,
remote_gateway, config.disable_proto, config.neighbour)
add_tunnels(tunnel_manager.new_iface_list)
else:
tunnel_manager = tunnel.BaseTunnelManager(control_socket,
cache, cert, address)
cache, cert, config.country, address)
cleanup.append(tunnel_manager.sock.close)
try:
......
......@@ -527,6 +527,10 @@ class RegistryServer(object):
msg = self._queryAddress(peer)
if msg is None:
return
# Remove country for old nodes
if self.getPeerProtocol(cn) < 7:
msg = ';'.join(','.join(a.split(',')[:3])
for a in msg.split(';'))
cert = self.getCert(cn)
msg = "%s %s" % (peer, msg)
logging.info("Sending bootstrap peer: %s", msg)
......
......@@ -199,7 +199,7 @@ class BaseTunnelManager(object):
_forward = None
_next_rina = True
def __init__(self, control_socket, cache, cert, address=()):
def __init__(self, control_socket, cache, cert, conf_country, address=()):
self.cert = cert
self._network = cert.network
self._prefix = cert.prefix
......@@ -208,6 +208,7 @@ class BaseTunnelManager(object):
self._connection_dict = {}
self._served = defaultdict(dict)
self._version = cache.version
self._conf_country = conf_country
address_dict = defaultdict(list)
for family, address in address:
......@@ -234,8 +235,9 @@ class BaseTunnelManager(object):
return
self._geoiplookup = geoiplookup
self._country = {}
for address in address_dict.itervalues():
self._updateCountry(address)
address_dict = {family: self._updateCountry(address)
for family, address in address_dict.iteritems()}
elif cache.same_country:
sys.exit("Can not respect 'same_country' network configuration"
" (GEOIP2_MMDB not set)")
......@@ -462,6 +464,11 @@ class BaseTunnelManager(object):
return
self._makeTunnel(peer, msg)
else:
if peer:
# Don't send country to old nodes
if self._getPeer(peer).protocol < 7:
return ';'.join(','.join(a.split(',')[:3]) for a in
';'.join(self._address.itervalues()).split(';'))
return ';'.join(self._address.itervalues())
elif not code: # network version
if peer:
......@@ -653,17 +660,19 @@ class BaseTunnelManager(object):
break
def _updateCountry(self, address):
for address in address:
family, ip = resolve(*address)
for ip in ip:
country = self._geoiplookup(ip)
if country:
if self._country.get(family) != country:
self._country[family] = country
logging.info('%s country: %s (%s)',
family_dict[family], country, ip)
return
def update():
for a in address:
family, ip = resolve(*a)
for ip in ip:
country = self._geoiplookup(ip)
if country:
if self._country.get(family) != country:
self._country[family] = country
logging.info('%s country: %s (%s)',
family_dict[family], country, ip)
return country
country = self._conf_country or update()
return [a + (country,) for a in address] if country else address
class TunnelManager(BaseTunnelManager):
......@@ -671,10 +680,10 @@ class TunnelManager(BaseTunnelManager):
'client_count', 'max_clients', 'same_country', 'tunnel_refresh'))
def __init__(self, control_socket, cache, cert, openvpn_args,
timeout, client_count, iface_list, address, ip_changed,
timeout, client_count, iface_list, country, address, ip_changed,
remote_gateway, disable_proto, neighbour_list=()):
super(TunnelManager, self).__init__(control_socket,
cache, cert, address)
cache, cert, country, address)
self.ovpn_args = openvpn_args
self.timeout = timeout
self._read_sock, self.write_sock = socket.socketpair(
......@@ -869,11 +878,12 @@ class TunnelManager(BaseTunnelManager):
if x[2] in self._disable_proto:
continue
if same_country:
family, ip = resolve(*x)
my_country = self._country.get(family)
family, ip = resolve(*x[:3])
my_country = self._country.get(family, self._conf_country)
if my_country:
for ip in ip:
country = self._geoiplookup(ip)
# Use geoip if there is no country in the address
country = x[3] if len(x) > 3 else self._geoiplookup(ip)
if country and (country != my_country
if my_country in same_country else
country in same_country):
......@@ -882,7 +892,7 @@ class TunnelManager(BaseTunnelManager):
else:
address_list.append((ip, x[1], x[2]))
continue
address_list.append(x)
address_list.append(x[:3])
self.cache.connecting(prefix, 1)
if not address_list:
return False
......@@ -1018,9 +1028,9 @@ class TunnelManager(BaseTunnelManager):
if self._ip_changed:
family, address = self._ip_changed(ip)
if address:
if self._geoiplookup or self._conf_country:
address = self._updateCountry(address)
self._address[family] = utils.dump_address(address)
if self._geoiplookup:
self._updateCountry(address)
self.cache.my_address = ';'.join(self._address.itervalues())
def broadcastNewVersion(self):
......
......@@ -233,12 +233,13 @@ def ipFromBin(ip, suffix=''):
def dump_address(address):
return ';'.join(map(','.join, address))
# Yield ip, port, protocol, and country if it is in the address
def parse_address(address_list):
for address in address_list.split(';'):
try:
a = ip, port, proto = address.split(',')
int(port)
yield a
a = address.split(',')
int(a[1]) # Check if port is an int
yield tuple(a[:4])
except ValueError, e:
logging.warning("Failed to parse node address %r (%s)",
address, e)
......
......@@ -32,7 +32,7 @@ if dirty:
# they are intended to the network admin.
# Only 'protocol' is important and it must be increased whenever they would be
# a wish to force an update of nodes.
protocol = 6
protocol = 7
min_protocol = 1
if __name__ == "__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