From 7c6f2b9373a33f7123d6c674c8e2d1c0b25148e3 Mon Sep 17 00:00:00 2001 From: Jocelyn Delande Date: Sun, 27 Sep 2015 01:16:56 +0200 Subject: [PATCH] [enh] Determine the public IPv6 locally Avoid depending on a remote service to fetch the globally routable IPv6 address. discussed in #89 --- debian/control | 3 ++- lib/yunohost/dyndns.py | 46 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/debian/control b/debian/control index ee8391364..989cc26b0 100644 --- a/debian/control +++ b/debian/control @@ -19,6 +19,7 @@ Depends: moulinette (>= 2.2.1), python-apt, ca-certificates, python-dnspython, - netcat-openbsd + netcat-openbsd, + iproute2 Description: YunoHost Python scripts Python functions to manage a YunoHost instance diff --git a/lib/yunohost/dyndns.py b/lib/yunohost/dyndns.py index 67b3db808..b4eb376dd 100644 --- a/lib/yunohost/dyndns.py +++ b/lib/yunohost/dyndns.py @@ -26,14 +26,39 @@ import os import sys import requests +import re import json import glob import base64 import errno +import subprocess from moulinette.core import MoulinetteError +class IPRouteLine(object): + """ Utility class to parse an ip route output line + + The output of ip ro is variable and hard to parse completly, it would + require a real parser, not just a regexp, so do minimal parsing here... + + >>> a = IPRouteLine('2001:: from :: via fe80::c23f:fe:1e:cafe dev eth0 src 2000:de:beef:ca:0:fe:1e:cafe metric 0') + >>> a.src_addr + "2000:de:beef:ca:0:fe:1e:cafe" + """ + regexp = re.compile( + r'(?Punreachable)?.*src\s+(?P[0-9a-f:]+).*') + + def __init__(self, line): + self.m = self.regexp.match(line) + if not self.m: + raise ValueError("Not a valid ip route get line") + + # make regexp group available as object attributes + for k, v in self.m.groupdict().items(): + setattr(self, k, v) + + def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None): """ Subscribe to a DynDNS service @@ -120,10 +145,23 @@ def dyndns_update(dyn_host="dynhost.yunohost.org", domain=None, key=None, ip=Non if ipv6 is None: new_ipv6 = None try: - new_ipv6 = requests.get('http://ip6.yunohost.org').text - except ConnectionError: - msignals.display(m18n.n('no_ipv6_connectivity'), - 'warning') + ip_route_out = subprocess.check_output( + ['ip', 'route', 'get', '2000::']).split('\n') + + if len(ip_route_out) > 0: + route = IPRouteLine(ip_route_out[0]) + if not route.unreachable: + new_ipv6 = route.src_addr + + except (OSError, ValueError) as e: + # Unlikely case "ip route" does not return status 0 + # or produces unexpected output + raise MoulinetteError(errno.EBADMSG, + "ip route cmd error : {}".format(e)) + + if new_ipv6 is None: + msignals.display(m18n.n('no_ipv6_connectivity'), 'warning') + else: new_ipv6 = ipv6