Mercurial > libervia-backend
comparison src/plugins/plugin_misc_ip.py @ 1537:6fa9e8c02c34
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
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 29 Sep 2015 17:54:25 +0200 |
parents | a5e0393a06cd |
children | eb8aae35085b |
comparison
equal
deleted
inserted
replaced
1536:04a13d9ae265 | 1537:6fa9e8c02c34 |
---|---|
20 from sat.core.i18n import _, D_ | 20 from sat.core.i18n import _, D_ |
21 from sat.core.constants import Const as C | 21 from sat.core.constants import Const as C |
22 from sat.core.log import getLogger | 22 from sat.core.log import getLogger |
23 log = getLogger(__name__) | 23 log = getLogger(__name__) |
24 from sat.tools import xml_tools | 24 from sat.tools import xml_tools |
25 from twisted.web.client import getPage | 25 from twisted.web import client as webclient |
26 from twisted.internet import defer | |
27 from twisted.internet import reactor | |
28 from twisted.internet import protocol | |
29 from twisted.internet import endpoints | |
26 import urlparse | 30 import urlparse |
31 try: | |
32 import netifaces | |
33 except ImportError: | |
34 log.warning(u"netifaces is not available, it help discovering IPs, you can install it on https://pypi.python.org/pypi/netifaces") | |
35 netifaces = None | |
27 | 36 |
28 | 37 |
29 PLUGIN_INFO = { | 38 PLUGIN_INFO = { |
30 "name": "IP discovery", | 39 "name": "IP discovery", |
31 "import_name": "IP", | 40 "import_name": "IP", |
32 "type": C.PLUG_TYPE_MISC, | 41 "type": C.PLUG_TYPE_MISC, |
42 "recommendations": ["NAT-PORT"], | |
33 "main": "IPPlugin", | 43 "main": "IPPlugin", |
34 "handler": "no", | 44 "handler": "no", |
35 "description": _("""This plugin help to discover our external IP address.""") | 45 "description": _("""This plugin help to discover our external IP address.""") |
36 } | 46 } |
37 | 47 |
60 </category> | 70 </category> |
61 </general> | 71 </general> |
62 </params> | 72 </params> |
63 """.format(category=GET_IP_CATEGORY, name=GET_IP_NAME, label=GET_IP_LABEL) | 73 """.format(category=GET_IP_CATEGORY, name=GET_IP_NAME, label=GET_IP_LABEL) |
64 | 74 |
75 | |
65 class IPPlugin(object): | 76 class IPPlugin(object): |
77 # TODO: refresh IP if a new connection is detected | |
78 # TODO: manage IPv6 when implemented in SàT | |
79 # TODO: implement XEP-0279 | |
66 | 80 |
67 def __init__(self, host): | 81 def __init__(self, host): |
68 log.info(_("plugin IP discovery initialization")) | 82 log.info(_("plugin IP discovery initialization")) |
69 self.host = host | 83 self.host = host |
70 host.memory.updateParams(PARAMS) | 84 host.memory.updateParams(PARAMS) |
71 | 85 |
72 def profileConnected(self, profile): | 86 # NAT-Port |
73 client = self.host.getClient(profile) | 87 try: |
74 # XXX: we keep cache only for profile session as ip can change between them | 88 self._nat = host.plugins['NAT-PORT'] |
75 client._ip_cache = None | 89 except KeyError: |
76 | 90 log.debug(u"NAT port plugin not available") |
77 def getIP(self, profile): | 91 self._nat = None |
78 """Try to discover external IP | 92 |
79 | 93 # XXX: cache is kept until SàT is restarted |
94 # if IP may have changed, use self.refreshIP | |
95 self._ip_cache = None | |
96 | |
97 def refreshIP(self): | |
98 # FIXME: use a trigger instead ? | |
99 self._ip_cache = None | |
100 | |
101 def _externalAllowed(self, profile): | |
102 """Return value of parameter with autorisation of user to do external requests | |
103 | |
104 if parameter is not set, a dialog is shown to use to get its confirmation, and parameted is set according to answer | |
80 @param profile: %(doc_profile)s | 105 @param profile: %(doc_profile)s |
81 @return (deferred): external IP address or None if it can't be discovered | 106 @return (defer.Deferred[bool]): True if external request is autorised |
82 """ | 107 """ |
83 client = self.host.getClient(profile) | |
84 if client._ip_cache is not None: | |
85 return client._ip_cache | |
86 | |
87 allow_get_ip = self.host.memory.params.getParamA(GET_IP_NAME, GET_IP_CATEGORY, use_default=False) | 108 allow_get_ip = self.host.memory.params.getParamA(GET_IP_NAME, GET_IP_CATEGORY, use_default=False) |
88 | 109 |
89 if allow_get_ip is None: | 110 if allow_get_ip is None: |
90 # we don't have autorisation from user yet to use get_ip, we ask him | 111 # we don't have autorisation from user yet to use get_ip, we ask him |
91 confirm_d = xml_tools.deferConfirm(self.host, _(GET_IP_CONFIRM), _(GET_IP_CONFIRM_TITLE), profile=profile) | |
92 def setParam(allowed): | 112 def setParam(allowed): |
93 # FIXME: we need to use boolConst as setParam only manage str/unicode | 113 # FIXME: we need to use boolConst as setParam only manage str/unicode |
94 # need to be fixed when params will be refactored | 114 # need to be fixed when params will be refactored |
95 self.host.memory.setParam(GET_IP_NAME, C.boolConst(allowed), GET_IP_CATEGORY) | 115 self.host.memory.setParam(GET_IP_NAME, C.boolConst(allow_get_ip), GET_IP_CATEGORY) |
96 return self.getIP(profile) | 116 return allowed |
97 | 117 d = xml_tools.deferConfirm(self.host, _(GET_IP_CONFIRM), _(GET_IP_CONFIRM_TITLE), profile=profile) |
98 return confirm_d.addCallback(setParam) | 118 d.addCallback(setParam) |
99 | 119 return d |
100 | 120 |
101 return getPage(GET_IP_PAGE) if allow_get_ip else None | 121 return defer.succeed(allow_get_ip) |
122 | |
123 def _filterAddresse(self, ip_addr): | |
124 """Filter acceptable addresses | |
125 | |
126 For now, just remove IPv4 local addresses | |
127 @param ip_addr(str): IP addresse | |
128 @return (bool): True if addresse is acceptable | |
129 """ | |
130 return not ip_addr.startswith('127.') | |
131 | |
132 def _insertFirst(self, addresses, ip_addr): | |
133 """Insert ip_addr as first item in addresses | |
134 | |
135 @param ip_addr(str): IP addresse | |
136 @param addresses(list): list of IP addresses | |
137 """ | |
138 if ip_addr in addresses: | |
139 if addresses[0] != ip_addr: | |
140 addresses.remove(ip_addr) | |
141 addresses.insert(0, ip_addr) | |
142 else: | |
143 addresses.insert(0, ip_addr) | |
144 | |
145 def _getIPFromExternal(self, ext_url): | |
146 """Get local IP by doing a connection on an external url | |
147 | |
148 @param ext_utl(str): url to connect to | |
149 @return (defer.Deferred): return local IP | |
150 """ | |
151 url = urlparse.urlparse(ext_url) | |
152 port = url.port | |
153 if port is None: | |
154 if url.scheme=='http': | |
155 port = 80 | |
156 elif url.scheme=='https': | |
157 port = 443 | |
158 else: | |
159 log.error(u"Unknown url scheme: {}".format(url.scheme)) | |
160 defer.returnValue(None) | |
161 if url.hostname is None: | |
162 log.error(u"Can't find url hostname for {}".format(GET_IP_PAGE)) | |
163 | |
164 point = endpoints.TCP4ClientEndpoint(reactor, url.hostname, port) | |
165 def gotConnection(p): | |
166 local_ip = p.transport.getHost().host | |
167 p.transport.loseConnection() | |
168 return local_ip | |
169 | |
170 d = endpoints.connectProtocol(point, protocol.Protocol()) | |
171 d.addCallback(gotConnection) | |
172 return d | |
173 | |
174 | |
175 @defer.inlineCallbacks | |
176 def getLocalIPs(self, profile): | |
177 """Try do discover local area network IPs | |
178 | |
179 @param profile): %(doc_profile)s | |
180 @return (deferred): list of lan IP addresses | |
181 or empty list if it can't be discovered | |
182 if there are several addresses, the one used with the server is put first | |
183 """ | |
184 # TODO: manage permission requesting (e.g. for UMTS link) | |
185 client = self.host.getClient(profile) | |
186 addresses = [] | |
187 | |
188 # we first try our luck with netifaces | |
189 if netifaces is not None: | |
190 addresses = [] | |
191 for interface in netifaces.interfaces(): | |
192 if_addresses = netifaces.ifaddresses(interface) | |
193 try: | |
194 inet_list = if_addresses[netifaces.AF_INET] | |
195 except KeyError: | |
196 continue | |
197 for data in inet_list: | |
198 addresse = data['addr'] | |
199 if self._filterAddresse(addresse): | |
200 addresses.append(addresse) | |
201 | |
202 # we first try with our connection to server | |
203 ip = client.xmlstream.transport.getHost().host | |
204 if self._filterAddresse(ip): | |
205 self._insertFirst(addresses, ip) | |
206 defer.returnValue(addresses) | |
207 | |
208 # if not available, we try with NAT-Port | |
209 if self._nat is not None: | |
210 nat_ip = yield self._nat.getIP(local=True) | |
211 if nat_ip is not None: | |
212 self._insertFirst(addresses, nat_ip) | |
213 defer.returnValue(addresses) | |
214 | |
215 if addresses: | |
216 defer.returnValue(addresses) | |
217 | |
218 # still not luck, we need to contact external website | |
219 allow_get_ip = yield self._externalAllowed(profile) | |
220 | |
221 if not allow_get_ip: | |
222 defer.returnValue(addresses) | |
223 | |
224 ip_tuple = yield self._getIPFromExternal(GET_IP_PAGE) | |
225 self._insertFirst(addresses, ip_tuple.local) | |
226 defer.returnValue(addresses) | |
227 | |
228 | |
229 @defer.inlineCallbacks | |
230 def getExternalIP(self, profile): | |
231 """Try to discover external IP | |
232 | |
233 @param profile: %(doc_profile)s | |
234 @return (deferred): external IP address or None if it can't be discovered | |
235 """ | |
236 if self._ip_cache is not None: | |
237 defer.returnValue(self._ip_cache) | |
238 | |
239 # we first try with NAT-Port | |
240 if self._nat is not None: | |
241 nat_ip = yield self._nat.getIP() | |
242 if nat_ip is not None: | |
243 defer.returnValue(nat_ip) | |
244 | |
245 # then by requesting external website | |
246 allow_get_ip = yield self._externalAllowed(profile) | |
247 ip = webclient.getPage(GET_IP_PAGE) if allow_get_ip else None | |
248 defer.returnValue(ip) |