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.
This commit is contained in:
Goffi 2020-02-09 23:56:42 +01:00
commit 104902f076

View file

@ -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