From 104902f0762708bae1229e733b148ec5b8594a3a Mon Sep 17 00:00:00 2001 From: Goffi Date: Sun, 9 Feb 2020 23:56:42 +0100 Subject: [PATCH] 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. --- sat/core/xmpp.py | 5 --- sat/plugins/plugin_misc_android.py | 63 ++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/sat/core/xmpp.py b/sat/core/xmpp.py index 0f69b718..1ed604f1 100644 --- a/sat/core/xmpp.py +++ b/sat/core/xmpp.py @@ -720,11 +720,6 @@ class SatXMPPClient(SatXMPPEntity, wokkel_client.XMPPClient): # 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)] diff --git a/sat/plugins/plugin_misc_android.py b/sat/plugins/plugin_misc_android.py index c77211ea..afc31fe2 100644 --- a/sat/plugins/plugin_misc_android.py +++ b/sat/plugins/plugin_misc_android.py @@ -25,10 +25,14 @@ from sat.core.i18n import _, D_ 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 @@ if sys.platform != "android": 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 @@ AndroidString = autoclass('java.lang.String') 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 @@ class AndroidPlugin(object): 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 @@ class AndroidPlugin(object): 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 @@ class AndroidPlugin(object): 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 @@ class AndroidPlugin(object): 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