# HG changeset patch # User Goffi # Date 1581289002 -3600 # Node ID b5c058c7692ec389036023179d92b848d3f6b023 # Parent be5fffe34987a397fe4a03068f2a6ac3f17a4fe1 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. diff -r be5fffe34987 -r b5c058c7692e sat/core/xmpp.py --- 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)] diff -r be5fffe34987 -r b5c058c7692e sat/plugins/plugin_misc_android.py --- 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