plugin IP discovery: better IP discovering:
- locals and external IP addresses can be discovered
- if netifaces is available, it is used
- same thing for NAT Port plugin
- cache is no more managed per profile but globally, a signal or observer will be used in the future to detect connection changes
This commit is contained in:
parent
1f29d5695d
commit
c23c6c39b8
1 changed files with 164 additions and 17 deletions
|
|
@ -22,14 +22,24 @@ from sat.core.constants import Const as C
|
|||
from sat.core.log import getLogger
|
||||
log = getLogger(__name__)
|
||||
from sat.tools import xml_tools
|
||||
from twisted.web.client import getPage
|
||||
from twisted.web import client as webclient
|
||||
from twisted.internet import defer
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet import protocol
|
||||
from twisted.internet import endpoints
|
||||
import urlparse
|
||||
try:
|
||||
import netifaces
|
||||
except ImportError:
|
||||
log.warning(u"netifaces is not available, it help discovering IPs, you can install it on https://pypi.python.org/pypi/netifaces")
|
||||
netifaces = None
|
||||
|
||||
|
||||
PLUGIN_INFO = {
|
||||
"name": "IP discovery",
|
||||
"import_name": "IP",
|
||||
"type": C.PLUG_TYPE_MISC,
|
||||
"recommendations": ["NAT-PORT"],
|
||||
"main": "IPPlugin",
|
||||
"handler": "no",
|
||||
"description": _("""This plugin help to discover our external IP address.""")
|
||||
|
|
@ -62,40 +72,177 @@ PARAMS = """
|
|||
</params>
|
||||
""".format(category=GET_IP_CATEGORY, name=GET_IP_NAME, label=GET_IP_LABEL)
|
||||
|
||||
|
||||
class IPPlugin(object):
|
||||
# TODO: refresh IP if a new connection is detected
|
||||
# TODO: manage IPv6 when implemented in SàT
|
||||
# TODO: implement XEP-0279
|
||||
|
||||
def __init__(self, host):
|
||||
log.info(_("plugin IP discovery initialization"))
|
||||
self.host = host
|
||||
host.memory.updateParams(PARAMS)
|
||||
|
||||
def profileConnected(self, profile):
|
||||
client = self.host.getClient(profile)
|
||||
# XXX: we keep cache only for profile session as ip can change between them
|
||||
client._ip_cache = None
|
||||
# NAT-Port
|
||||
try:
|
||||
self._nat = host.plugins['NAT-PORT']
|
||||
except KeyError:
|
||||
log.debug(u"NAT port plugin not available")
|
||||
self._nat = None
|
||||
|
||||
def getIP(self, profile):
|
||||
# XXX: cache is kept until SàT is restarted
|
||||
# if IP may have changed, use self.refreshIP
|
||||
self._ip_cache = None
|
||||
|
||||
def refreshIP(self):
|
||||
# FIXME: use a trigger instead ?
|
||||
self._ip_cache = None
|
||||
|
||||
def _externalAllowed(self, profile):
|
||||
"""Return value of parameter with autorisation of user to do external requests
|
||||
|
||||
if parameter is not set, a dialog is shown to use to get its confirmation, and parameted is set according to answer
|
||||
@param profile: %(doc_profile)s
|
||||
@return (defer.Deferred[bool]): True if external request is autorised
|
||||
"""
|
||||
allow_get_ip = self.host.memory.params.getParamA(GET_IP_NAME, GET_IP_CATEGORY, use_default=False)
|
||||
|
||||
if allow_get_ip is None:
|
||||
# we don't have autorisation from user yet to use get_ip, we ask him
|
||||
def setParam(allowed):
|
||||
# FIXME: we need to use boolConst as setParam only manage str/unicode
|
||||
# need to be fixed when params will be refactored
|
||||
self.host.memory.setParam(GET_IP_NAME, C.boolConst(allow_get_ip), GET_IP_CATEGORY)
|
||||
return allowed
|
||||
d = xml_tools.deferConfirm(self.host, _(GET_IP_CONFIRM), _(GET_IP_CONFIRM_TITLE), profile=profile)
|
||||
d.addCallback(setParam)
|
||||
return d
|
||||
|
||||
return defer.succeed(allow_get_ip)
|
||||
|
||||
def _filterAddresse(self, ip_addr):
|
||||
"""Filter acceptable addresses
|
||||
|
||||
For now, just remove IPv4 local addresses
|
||||
@param ip_addr(str): IP addresse
|
||||
@return (bool): True if addresse is acceptable
|
||||
"""
|
||||
return not ip_addr.startswith('127.')
|
||||
|
||||
def _insertFirst(self, addresses, ip_addr):
|
||||
"""Insert ip_addr as first item in addresses
|
||||
|
||||
@param ip_addr(str): IP addresse
|
||||
@param addresses(list): list of IP addresses
|
||||
"""
|
||||
if ip_addr in addresses:
|
||||
if addresses[0] != ip_addr:
|
||||
addresses.remove(ip_addr)
|
||||
addresses.insert(0, ip_addr)
|
||||
else:
|
||||
addresses.insert(0, ip_addr)
|
||||
|
||||
def _getIPFromExternal(self, ext_url):
|
||||
"""Get local IP by doing a connection on an external url
|
||||
|
||||
@param ext_utl(str): url to connect to
|
||||
@return (defer.Deferred): return local IP
|
||||
"""
|
||||
url = urlparse.urlparse(ext_url)
|
||||
port = url.port
|
||||
if port is None:
|
||||
if url.scheme=='http':
|
||||
port = 80
|
||||
elif url.scheme=='https':
|
||||
port = 443
|
||||
else:
|
||||
log.error(u"Unknown url scheme: {}".format(url.scheme))
|
||||
defer.returnValue(None)
|
||||
if url.hostname is None:
|
||||
log.error(u"Can't find url hostname for {}".format(GET_IP_PAGE))
|
||||
|
||||
point = endpoints.TCP4ClientEndpoint(reactor, url.hostname, port)
|
||||
def gotConnection(p):
|
||||
local_ip = p.transport.getHost().host
|
||||
p.transport.loseConnection()
|
||||
return local_ip
|
||||
|
||||
d = endpoints.connectProtocol(point, protocol.Protocol())
|
||||
d.addCallback(gotConnection)
|
||||
return d
|
||||
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def getLocalIPs(self, profile):
|
||||
"""Try do discover local area network IPs
|
||||
|
||||
@param profile): %(doc_profile)s
|
||||
@return (deferred): list of lan IP addresses
|
||||
or empty list if it can't be discovered
|
||||
if there are several addresses, the one used with the server is put first
|
||||
"""
|
||||
# TODO: manage permission requesting (e.g. for UMTS link)
|
||||
client = self.host.getClient(profile)
|
||||
addresses = []
|
||||
|
||||
# we first try our luck with netifaces
|
||||
if netifaces is not None:
|
||||
addresses = []
|
||||
for interface in netifaces.interfaces():
|
||||
if_addresses = netifaces.ifaddresses(interface)
|
||||
try:
|
||||
inet_list = if_addresses[netifaces.AF_INET]
|
||||
except KeyError:
|
||||
continue
|
||||
for data in inet_list:
|
||||
addresse = data['addr']
|
||||
if self._filterAddresse(addresse):
|
||||
addresses.append(addresse)
|
||||
|
||||
# we first try with our connection to server
|
||||
ip = client.xmlstream.transport.getHost().host
|
||||
if self._filterAddresse(ip):
|
||||
self._insertFirst(addresses, ip)
|
||||
defer.returnValue(addresses)
|
||||
|
||||
# if not available, we try with NAT-Port
|
||||
if self._nat is not None:
|
||||
nat_ip = yield self._nat.getIP(local=True)
|
||||
if nat_ip is not None:
|
||||
self._insertFirst(addresses, nat_ip)
|
||||
defer.returnValue(addresses)
|
||||
|
||||
if addresses:
|
||||
defer.returnValue(addresses)
|
||||
|
||||
# still not luck, we need to contact external website
|
||||
allow_get_ip = yield self._externalAllowed(profile)
|
||||
|
||||
if not allow_get_ip:
|
||||
defer.returnValue(addresses)
|
||||
|
||||
ip_tuple = yield self._getIPFromExternal(GET_IP_PAGE)
|
||||
self._insertFirst(addresses, ip_tuple.local)
|
||||
defer.returnValue(addresses)
|
||||
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def getExternalIP(self, profile):
|
||||
"""Try to discover external IP
|
||||
|
||||
@param profile: %(doc_profile)s
|
||||
@return (deferred): external IP address or None if it can't be discovered
|
||||
"""
|
||||
client = self.host.getClient(profile)
|
||||
if client._ip_cache is not None:
|
||||
return client._ip_cache
|
||||
if self._ip_cache is not None:
|
||||
defer.returnValue(self._ip_cache)
|
||||
|
||||
allow_get_ip = self.host.memory.params.getParamA(GET_IP_NAME, GET_IP_CATEGORY, use_default=False)
|
||||
# we first try with NAT-Port
|
||||
if self._nat is not None:
|
||||
nat_ip = yield self._nat.getIP()
|
||||
if nat_ip is not None:
|
||||
defer.returnValue(nat_ip)
|
||||
|
||||
if allow_get_ip is None:
|
||||
# we don't have autorisation from user yet to use get_ip, we ask him
|
||||
confirm_d = xml_tools.deferConfirm(self.host, _(GET_IP_CONFIRM), _(GET_IP_CONFIRM_TITLE), profile=profile)
|
||||
def setParam(allowed):
|
||||
# FIXME: we need to use boolConst as setParam only manage str/unicode
|
||||
# need to be fixed when params will be refactored
|
||||
self.host.memory.setParam(GET_IP_NAME, C.boolConst(allowed), GET_IP_CATEGORY)
|
||||
return self.getIP(profile)
|
||||
|
||||
return confirm_d.addCallback(setParam)
|
||||
|
||||
|
||||
return getPage(GET_IP_PAGE) if allow_get_ip else None
|
||||
# then by requesting external website
|
||||
allow_get_ip = yield self._externalAllowed(profile)
|
||||
ip = webclient.getPage(GET_IP_PAGE) if allow_get_ip else None
|
||||
defer.returnValue(ip)
|
||||
|
|
|
|||
Loading…
Reference in a new issue