diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index f38d1fadf..3259c6a4a 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -1,9 +1,12 @@ #!/usr/bin/env python import os +import random from moulinette import m18n from moulinette.utils.network import download_text +from moulinette.utils.process import check_output +from moulinette.utils.filesystem import read_file from yunohost.diagnosis import Diagnoser @@ -24,7 +27,12 @@ class IPDiagnoser(Diagnoser): versions = self.args["versions"] if 4 in versions: - ipv4 = self.get_public_ip(4) + + if not self.can_ping_outside(4): + ipv4 = None + else: + ipv4 = self.get_public_ip(4) + yield dict(meta = {"version": 4}, data = ipv4, status = "SUCCESS" if ipv4 else "ERROR", @@ -32,15 +40,57 @@ class IPDiagnoser(Diagnoser): else ("diagnosis_network_no_ipv4", {})) if 6 in versions: - ipv6 = self.get_public_ip(6) + + if not self.can_ping_outside(4): + ipv6 = None + else: + ipv6 = self.get_public_ip(6) + yield dict(meta = {"version": 6}, data = ipv6, status = "SUCCESS" if ipv6 else "WARNING", summary = ("diagnosis_network_connected_ipv6", {}) if ipv6 \ else ("diagnosis_network_no_ipv6", {})) + + def can_ping_outside(self, protocol=4): + + assert protocol in [4, 6], "Invalid protocol version, it should be either 4 or 6 and was '%s'" % repr(protocol) + + # We can know that ipv6 is not available directly if this file does not exists + if protocol == 6 and not os.path.exists("/proc/net/if_inet6"): + return False + + # If we are indeed connected in ipv4 or ipv6, we should find a default route + routes = check_output("ip -%s route" % protocol).split("\n") + if not [r for r in routes if r.startswith("default")]: + return False + + # We use the resolver file as a list of well-known, trustable (ie not google ;)) IPs that we can ping + resolver_file = "/usr/share/yunohost/templates/dnsmasq/plain/resolv.dnsmasq.conf" + resolvers = [r.split(" ")[1] for r in read_file(resolver_file).split("\n") if r.startswith("nameserver")] + + if protocol == 4: + resolvers = [r for r in resolvers if ":" not in r] + if protocol == 6: + resolvers = [r for r in resolvers if ":" in r] + + assert resolvers != [], "Uhoh, need at least one IPv%s DNS resolver in %s ..." % (protocol, resolver_file) + + # So let's try to ping the first 4~5 resolvers (shuffled) + # If we succesfully ping any of them, we conclude that we are indeed connected + def ping(protocol, target): + return os.system("ping -c1 -%s -W 3 %s >/dev/null 2>/dev/null" % (protocol, target)) == 0 + + random.shuffle(resolvers) + return any(ping(protocol, resolver) for resolver in resolvers[:5]) + def get_public_ip(self, protocol=4): + # FIXME - TODO : here we assume that DNS resolution for ip.yunohost.org is working + # but if we want to be able to diagnose DNS resolution issues independently from + # internet connectivity, we gotta rely on fixed IPs first.... + if protocol == 4: url = 'https://ip.yunohost.org' elif protocol == 6: diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 3ba64445d..c8b81fd2c 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -79,6 +79,9 @@ class DNSRecordsDiagnoser(Diagnoser): command = "dig +short @%s %s %s" % (self.resolver, type_, domain) else: command = "dig +short @%s %s %s.%s" % (self.resolver, type_, name, domain) + # FIXME : gotta handle case where this command fails ... + # e.g. no internet connectivity (dependency mechanism to good result from 'ip' diagosis ?) + # or the resolver is unavailable for some reason output = check_output(command).strip() output = output.replace("\;",";") if output.startswith('"') and output.endswith('"'): diff --git a/locales/en.json b/locales/en.json index 0bb6d7275..ae5e4dc53 100644 --- a/locales/en.json +++ b/locales/en.json @@ -159,8 +159,8 @@ "diagnosis_network_no_ipv4": "The server does not have a working IPv4.", "diagnosis_network_connected_ipv6": "The server is connect to the Internet through IPv6 !", "diagnosis_network_no_ipv6": "The server does not have a working IPv6.", - "diagnosis_dns_good_conf": "Good DNS configuration for {domain} : {category}.", - "diagnosis_dns_bad_conf": "Bad DNS configuration for {domain} : {category}.", + "diagnosis_dns_good_conf": "Good DNS configuration for domain {domain} (category {category})", + "diagnosis_dns_bad_conf": "Bad DNS configuration for domain {domain} (category {category})", "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type {0}, name {1} and value {2}", "diagnosis_dns_discrepancy": "According to the recommended DNS configuration, the value for the DNS record with type {0} and name {1} should be {2}, not {3}.", "diagnosis_description_ip": "Internet connectivity",