comparison sat/plugins/plugin_misc_android.py @ 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 f2d3ab4390a3
children dcebc585c29f
comparison
equal deleted inserted replaced
3161:be5fffe34987 3162:b5c058c7692e
23 from pathlib import Path 23 from pathlib import Path
24 from sat.core.i18n import _, D_ 24 from sat.core.i18n import _, D_
25 from sat.core.constants import Const as C 25 from sat.core.constants import Const as C
26 from sat.core.log import getLogger 26 from sat.core.log import getLogger
27 from sat.core import exceptions 27 from sat.core import exceptions
28 from sat.tools.common import async_process
28 from sat.memory import params 29 from sat.memory import params
30 from twisted.names import client as dns_client
31 from twisted.python.procutils import which
29 from twisted.internet import defer 32 from twisted.internet import defer
30 from twisted.internet import reactor 33 from twisted.internet import reactor
31 from twisted.internet import protocol 34 from twisted.internet import protocol
35 from twisted.internet import abstract
32 from twisted.internet import error as int_error 36 from twisted.internet import error as int_error
33 37
34 log = getLogger(__name__) 38 log = getLogger(__name__)
35 39
36 PLUGIN_INFO = { 40 PLUGIN_INFO = {
47 51
48 if sys.platform != "android": 52 if sys.platform != "android":
49 raise exceptions.CancelError("this module is not needed on this platform") 53 raise exceptions.CancelError("this module is not needed on this platform")
50 54
51 55
56 import re
52 from plyer import vibrator 57 from plyer import vibrator
58 from android import api_version
53 from plyer.platforms.android import activity 59 from plyer.platforms.android import activity
54 from plyer.platforms.android.notification import AndroidNotification 60 from plyer.platforms.android.notification import AndroidNotification
55 from jnius import autoclass 61 from jnius import autoclass
56 from android.broadcast import BroadcastReceiver 62 from android.broadcast import BroadcastReceiver
57 from android import python_act 63 from android import python_act
64 70
65 # notifications 71 # notifications
66 AndroidString = autoclass('java.lang.String') 72 AndroidString = autoclass('java.lang.String')
67 PendingIntent = autoclass('android.app.PendingIntent') 73 PendingIntent = autoclass('android.app.PendingIntent')
68 Intent = autoclass('android.content.Intent') 74 Intent = autoclass('android.content.Intent')
75
76 # DNS
77 # regex to find dns server prop with "getprop"
78 RE_DNS = re.compile(r"^\[net\.[a-z0-9]+\.dns[0-4]\]: \[(.*)\]$", re.MULTILINE)
79 SystemProperties = autoclass('android.os.SystemProperties')
69 80
70 #: delay between a pause event and sending the inactive indication to server, in seconds 81 #: delay between a pause event and sending the inactive indication to server, in seconds
71 #: we don't send the indication immediately because user can be just checking something 82 #: we don't send the indication immediately because user can be just checking something
72 #: quickly on an other app. 83 #: quickly on an other app.
73 CSI_DELAY = 30 84 CSI_DELAY = 30
211 ring_options=params.makeOptions(RING_OPTS, "normal"), 222 ring_options=params.makeOptions(RING_OPTS, "normal"),
212 ) 223 )
213 224
214 def __init__(self, host): 225 def __init__(self, host):
215 log.info(_("plugin Android initialization")) 226 log.info(_("plugin Android initialization"))
227 log.info(f"using Android API {api_version}")
216 self.host = host 228 self.host = host
217 self._csi = host.plugins.get('XEP-0352') 229 self._csi = host.plugins.get('XEP-0352')
218 self._csi_timer = None 230 self._csi_timer = None
219 host.memory.updateParams(self.params) 231 host.memory.updateParams(self.params)
220 try: 232 try:
261 self.notif_player = MediaPlayer() 273 self.notif_player = MediaPlayer()
262 self.notif_player.setDataSource(str(notif_path)) 274 self.notif_player.setDataSource(str(notif_path))
263 self.notif_player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION) 275 self.notif_player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION)
264 self.notif_player.prepare() 276 self.notif_player.prepare()
265 277
278 # DNS fix
279 defer.ensureDeferred(self.updateResolver())
280
266 # Connectivity handling 281 # Connectivity handling
267 self.cm = activity.getSystemService(Context.CONNECTIVITY_SERVICE) 282 self.cm = activity.getSystemService(Context.CONNECTIVITY_SERVICE)
268 self._net_type = None 283 self._net_type = None
269 d = defer.ensureDeferred(self._checkConnectivity()) 284 d = defer.ensureDeferred(self._checkConnectivity())
270 d.addErrback(host.logErrback) 285 d.addErrback(host.logErrback)
422 log.debug(f"handling network change ({net_type})") 437 log.debug(f"handling network change ({net_type})")
423 if net_type == NET_TYPE_NONE: 438 if net_type == NET_TYPE_NONE:
424 for client in self.host.getClients(C.PROF_KEY_ALL): 439 for client in self.host.getClients(C.PROF_KEY_ALL):
425 client.networkDisabled() 440 client.networkDisabled()
426 else: 441 else:
442 # DNS servers may have changed
443 await self.updateResolver()
427 # client may be there but disabled (e.g. with stream management) 444 # client may be there but disabled (e.g. with stream management)
428 for client in self.host.getClients(C.PROF_KEY_ALL): 445 for client in self.host.getClients(C.PROF_KEY_ALL):
429 log.debug(f"enabling network for {client.profile}") 446 log.debug(f"enabling network for {client.profile}")
430 client.networkEnabled() 447 client.networkEnabled()
431 448
476 493
477 def onConnectivityChange(self): 494 def onConnectivityChange(self):
478 log.debug("onConnectivityChange called") 495 log.debug("onConnectivityChange called")
479 d = defer.ensureDeferred(self._checkConnectivity()) 496 d = defer.ensureDeferred(self._checkConnectivity())
480 d.addErrback(self.host.logErrback) 497 d.addErrback(self.host.logErrback)
498
499 async def updateResolver(self):
500 # There is no "/etc/resolv.conf" on Android, which confuse Twisted and makes
501 # SRV record checking unusable. We fixe that by checking DNS server used, and
502 # updating Twisted's resolver accordingly
503 dns_servers = await self.getDNSServers()
504
505 log.info(
506 "Patching Twisted to use Android DNS resolver ({dns_servers})".format(
507 dns_servers=', '.join([s[0] for s in dns_servers]))
508 )
509 dns_client.theResolver = dns_client.createResolver(servers=dns_servers)
510
511 async def getDNSServers(self):
512 servers = []
513
514 if api_version < 26:
515 # thanks to A-IV at https://stackoverflow.com/a/11362271 for the way to go
516 log.debug("Old API, using SystemProperties to find DNS")
517 for idx in range(1, 5):
518 addr = SystemProperties.get(f'net.dns{idx}')
519 if abstract.isIPAddress(addr):
520 servers.append((addr, 53))
521 else:
522 log.debug(f"API {api_version} >= 26, using getprop to find DNS")
523 # use of getprop inspired by various solutions at
524 # https://stackoverflow.com/q/3070144
525 # it's the most simple option, and it fit wells with async_process
526 getprop_paths = which('getprop')
527 if getprop_paths:
528 try:
529 getprop_path = getprop_paths[0]
530 props = await async_process.run(getprop_path)
531 servers = [(ip, 53) for ip in RE_DNS.findall(props.decode())
532 if abstract.isIPAddress(ip)]
533 except Exception as e:
534 log.warning(f"Can't use \"getprop\" to find DNS server: {e}")
535 if not servers:
536 # FIXME: Cloudflare's 1.1.1.1 seems to have a better privacy policy, to be
537 # checked.
538 log.warning(
539 "no server found, we have to use factory Google DNS, this is not ideal "
540 "for privacy"
541 )
542 servers.append(('8.8.8.8', 53), ('8.8.4.4', 53))
543 return servers