Mercurial > libervia-backend
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 |