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