changeset 3162:b5c058c7692e

core (xmpp), plugin android: fixed DNS, SRV handling on Android: Android doesn't have a "/etc/resolv.conf" that Twisted expect. As a result, SRV records can't be checked correctly on connection. To work around that, DNS servers used on Android are checked when necessary (on Android plugin startup and on connectivity change), using some hacks, and Twisted is patched to use them.
author Goffi <goffi@goffi.org>
date Sun, 09 Feb 2020 23:56:42 +0100
parents be5fffe34987
children d10b2368684e
files sat/core/xmpp.py sat/plugins/plugin_misc_android.py
diffstat 2 files changed, 63 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/sat/core/xmpp.py	Sun Feb 09 23:56:40 2020 +0100
+++ b/sat/core/xmpp.py	Sun Feb 09 23:56:42 2020 +0100
@@ -720,11 +720,6 @@
         # bridge
         self.identities = [disco.DiscoIdentity("client", "pc", C.APP_NAME)]
         if sys.platform == "android":
-            # FIXME: temporary hack as SRV is not working on android
-            # TODO: remove this hack and fix SRV
-            log.info("FIXME: Android hack, ignoring SRV")
-            if host is None:
-                host = user_jid.host
             # for now we consider Android devices to be always phones
             self.identities = [disco.DiscoIdentity("client", "phone", C.APP_NAME)]
 
--- a/sat/plugins/plugin_misc_android.py	Sun Feb 09 23:56:40 2020 +0100
+++ b/sat/plugins/plugin_misc_android.py	Sun Feb 09 23:56:42 2020 +0100
@@ -25,10 +25,14 @@
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
 from sat.core import exceptions
+from sat.tools.common import async_process
 from sat.memory import params
+from twisted.names import client as dns_client
+from twisted.python.procutils import which
 from twisted.internet import defer
 from twisted.internet import reactor
 from twisted.internet import protocol
+from twisted.internet import abstract
 from twisted.internet import error as int_error
 
 log = getLogger(__name__)
@@ -49,7 +53,9 @@
     raise exceptions.CancelError("this module is not needed on this platform")
 
 
+import re
 from plyer import vibrator
+from android import api_version
 from plyer.platforms.android import activity
 from plyer.platforms.android.notification import AndroidNotification
 from jnius import autoclass
@@ -67,6 +73,11 @@
 PendingIntent = autoclass('android.app.PendingIntent')
 Intent = autoclass('android.content.Intent')
 
+# DNS
+# regex to find dns server prop with "getprop"
+RE_DNS = re.compile(r"^\[net\.[a-z0-9]+\.dns[0-4]\]: \[(.*)\]$", re.MULTILINE)
+SystemProperties = autoclass('android.os.SystemProperties')
+
 #: delay between a pause event and sending the inactive indication to server, in seconds
 #: we don't send the indication immediately because user can be just checking something
 #: quickly on an other app.
@@ -213,6 +224,7 @@
 
     def __init__(self, host):
         log.info(_("plugin Android initialization"))
+        log.info(f"using Android API {api_version}")
         self.host = host
         self._csi = host.plugins.get('XEP-0352')
         self._csi_timer = None
@@ -263,6 +275,9 @@
         self.notif_player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION)
         self.notif_player.prepare()
 
+        # DNS fix
+        defer.ensureDeferred(self.updateResolver())
+
         # Connectivity handling
         self.cm = activity.getSystemService(Context.CONNECTIVITY_SERVICE)
         self._net_type = None
@@ -424,6 +439,8 @@
             for client in self.host.getClients(C.PROF_KEY_ALL):
                 client.networkDisabled()
         else:
+            # DNS servers may have changed
+            await self.updateResolver()
             # client may be there but disabled (e.g. with stream management)
             for client in self.host.getClients(C.PROF_KEY_ALL):
                 log.debug(f"enabling network for {client.profile}")
@@ -478,3 +495,49 @@
         log.debug("onConnectivityChange called")
         d = defer.ensureDeferred(self._checkConnectivity())
         d.addErrback(self.host.logErrback)
+
+    async def updateResolver(self):
+        # There is no "/etc/resolv.conf" on Android, which confuse Twisted and makes
+        # SRV record checking unusable. We fixe that by checking DNS server used, and
+        # updating Twisted's resolver accordingly
+        dns_servers = await self.getDNSServers()
+
+        log.info(
+            "Patching Twisted to use Android DNS resolver ({dns_servers})".format(
+            dns_servers=', '.join([s[0] for s in dns_servers]))
+        )
+        dns_client.theResolver = dns_client.createResolver(servers=dns_servers)
+
+    async def getDNSServers(self):
+        servers = []
+
+        if api_version < 26:
+            # thanks to A-IV at https://stackoverflow.com/a/11362271 for the way to go
+            log.debug("Old API, using SystemProperties to find DNS")
+            for idx in range(1, 5):
+                addr = SystemProperties.get(f'net.dns{idx}')
+                if abstract.isIPAddress(addr):
+                    servers.append((addr, 53))
+        else:
+            log.debug(f"API {api_version} >= 26, using getprop to find DNS")
+            # use of getprop inspired by various solutions at
+            # https://stackoverflow.com/q/3070144
+            # it's the most simple option, and it fit wells with async_process
+            getprop_paths = which('getprop')
+            if getprop_paths:
+                try:
+                    getprop_path = getprop_paths[0]
+                    props = await async_process.run(getprop_path)
+                    servers = [(ip, 53) for ip in RE_DNS.findall(props.decode())
+                               if abstract.isIPAddress(ip)]
+                except Exception as e:
+                    log.warning(f"Can't use \"getprop\" to find DNS server: {e}")
+        if not servers:
+            # FIXME: Cloudflare's 1.1.1.1 seems to have a better privacy policy, to be
+            #   checked.
+            log.warning(
+                "no server found, we have to use factory Google DNS, this is not ideal "
+                "for privacy"
+            )
+            servers.append(('8.8.8.8', 53), ('8.8.4.4', 53))
+        return servers