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