From d6cf561cd32a095bf2e5124e7696cda1c1364783 Mon Sep 17 00:00:00 2001 From: brubbel <5667690+brubbel@users.noreply.github.com> Date: Thu, 30 Aug 2018 16:11:12 +0200 Subject: [PATCH 1/4] discovery service: dynamically replace 0.0.0.0 endpointUrl in server response --- opcua/server/internal_server.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/opcua/server/internal_server.py b/opcua/server/internal_server.py index 875b31ca2..6bd758fb2 100644 --- a/opcua/server/internal_server.py +++ b/opcua/server/internal_server.py @@ -5,6 +5,7 @@ from datetime import datetime, timedelta from copy import copy +import pickle import os import logging from threading import Lock @@ -181,6 +182,20 @@ def get_endpoints(self, params=None, sockname=None): return self.endpoints[:] def find_servers(self, params): + servers = self._find_servers(params) + if not params.EndpointUrl: + return servers + # If 0.0.0.0 in any endpointUrl, copy endPointUrl from + # FindServersParameters provided in the client request. + servers = pickle.loads(pickle.dumps(servers)) + for serv in servers: + for idx, url in enumerate(serv.DiscoveryUrls): + if '0.0.0.0' not in url: + continue + serv.DiscoveryUrls[idx] = params.EndpointUrl + return servers + + def _find_servers(self, params): if not params.ServerUris: return [desc.Server for desc in self._known_servers.values()] servers = [] From 843708fececead55b9ede1cf965865d99d125b92 Mon Sep 17 00:00:00 2001 From: brubbel <5667690+brubbel@users.noreply.github.com> Date: Sat, 1 Sep 2018 20:56:44 +0200 Subject: [PATCH 2/4] get_endpoints(): dynamically replace 0.0.0.0 with endpointUrl in client req. --- opcua/server/internal_server.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/opcua/server/internal_server.py b/opcua/server/internal_server.py index 6bd758fb2..3004f2346 100644 --- a/opcua/server/internal_server.py +++ b/opcua/server/internal_server.py @@ -169,17 +169,18 @@ def add_endpoint(self, endpoint): def get_endpoints(self, params=None, sockname=None): self.logger.info("get endpoint") - if sockname: - # return to client the ip address it has access to - edps = [] - for edp in self.endpoints: - edp1 = copy(edp) + # return to client the ip address it has access to + edps = [] + for edp in self.endpoints: + edp1 = copy(edp) + if '0.0.0.0' in edp1.EndpointUrl and params.EndpointUrl: + edp1.EndpointUrl = params.EndpointUrl + elif sockname: url = urlparse(edp1.EndpointUrl) url = url._replace(netloc=sockname[0] + ":" + str(sockname[1])) edp1.EndpointUrl = url.geturl() - edps.append(edp1) - return edps - return self.endpoints[:] + edps.append(edp1) + return edps def find_servers(self, params): servers = self._find_servers(params) @@ -326,7 +327,7 @@ def create_session(self, params, sockname=None): result.MaxRequestMessageSize = 65536 self.nonce = utils.create_nonce(32) result.ServerNonce = self.nonce - result.ServerEndpoints = self.get_endpoints(sockname=sockname) + result.ServerEndpoints = self.get_endpoints(params=params, sockname=sockname) return result From f95a9fe80558482f3ad511d28a6010f08c9a6653 Mon Sep 17 00:00:00 2001 From: brubbel <5667690+brubbel@users.noreply.github.com> Date: Sun, 2 Sep 2018 15:38:55 +0200 Subject: [PATCH 3/4] get_endpoints(): support for IPv6 --- opcua/server/internal_server.py | 67 +++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/opcua/server/internal_server.py b/opcua/server/internal_server.py index 3004f2346..b30ece0cc 100644 --- a/opcua/server/internal_server.py +++ b/opcua/server/internal_server.py @@ -4,12 +4,14 @@ """ from datetime import datetime, timedelta -from copy import copy import pickle import os import logging from threading import Lock from enum import Enum +from socket import INADDR_ANY # IPv4 '0.0.0.0' +IN6ADDR_ANY = '::' +from ipaddress import ip_address try: from urllib.parse import urlparse except ImportError: @@ -169,34 +171,51 @@ def add_endpoint(self, endpoint): def get_endpoints(self, params=None, sockname=None): self.logger.info("get endpoint") - # return to client the ip address it has access to - edps = [] - for edp in self.endpoints: - edp1 = copy(edp) - if '0.0.0.0' in edp1.EndpointUrl and params.EndpointUrl: - edp1.EndpointUrl = params.EndpointUrl - elif sockname: - url = urlparse(edp1.EndpointUrl) - url = url._replace(netloc=sockname[0] + ":" + str(sockname[1])) - edp1.EndpointUrl = url.geturl() - edps.append(edp1) - return edps + # return to client the endpoints it has access to + edps = pickle.loads(pickle.dumps(self.endpoints)) + netloc = self._get_netloc(params, sockname) + return [self._endpoint_replace_inaddr_any(edp, netloc) for edp in edps] + + @staticmethod + def _get_netloc(params=None, sockname=None): + # find the ip:port as seen by our client. + netloc = None + if params and params.EndpointUrl: + # use ip:port as provided within client request params. + netloc = urlparse(params.EndpointUrl).netloc + if not netloc and sockname: + # use ip:port extracted from our local interface. + netloc = sockname[0] + ":" + str(sockname[1]) + return netloc + + @staticmethod + def _endpoint_replace_inaddr_any(edp, netloc): + edp.EndpointUrl = InternalServer._replace_inaddr_any(edp.EndpointUrl, netloc) + return edp + + @staticmethod + def _replace_inaddr_any(urlStr, netloc): + # If urlStr is '0.0.0.0:port' or '[::]:port', use netloc ip:port. + parseResult = urlparse(urlStr) + try: + hostip = ip_address(parseResult.hostname) + except ValueError: + hostip = None + if not (hostip and netloc): + pass + elif ip_address(hostip) in (ip_address(INADDR_ANY), ip_address(IN6ADDR_ANY)): + urlStr = parseResult._replace(netloc=netloc).geturl() + return urlStr def find_servers(self, params): - servers = self._find_servers(params) - if not params.EndpointUrl: - return servers - # If 0.0.0.0 in any endpointUrl, copy endPointUrl from - # FindServersParameters provided in the client request. + servers = self._filter_servers(params) servers = pickle.loads(pickle.dumps(servers)) - for serv in servers: - for idx, url in enumerate(serv.DiscoveryUrls): - if '0.0.0.0' not in url: - continue - serv.DiscoveryUrls[idx] = params.EndpointUrl + netloc = self._get_netloc(params) + for srv in servers: + srv.DiscoveryUrls = [self._replace_inaddr_any(url, netloc) for url in srv.DiscoveryUrls] return servers - def _find_servers(self, params): + def _filter_servers(self, params): if not params.ServerUris: return [desc.Server for desc in self._known_servers.values()] servers = [] From f8f2027b883c4e3bba4d30ceb4fb3a888bd92777 Mon Sep 17 00:00:00 2001 From: brubbel <5667690+brubbel@users.noreply.github.com> Date: Sun, 2 Sep 2018 16:44:18 +0200 Subject: [PATCH 4/4] pickle -> deepcopy() --- opcua/server/internal_server.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/opcua/server/internal_server.py b/opcua/server/internal_server.py index b30ece0cc..bc158f965 100644 --- a/opcua/server/internal_server.py +++ b/opcua/server/internal_server.py @@ -4,7 +4,7 @@ """ from datetime import datetime, timedelta -import pickle +from copy import deepcopy import os import logging from threading import Lock @@ -172,9 +172,11 @@ def add_endpoint(self, endpoint): def get_endpoints(self, params=None, sockname=None): self.logger.info("get endpoint") # return to client the endpoints it has access to - edps = pickle.loads(pickle.dumps(self.endpoints)) netloc = self._get_netloc(params, sockname) - return [self._endpoint_replace_inaddr_any(edp, netloc) for edp in edps] + edps = deepcopy(self.endpoints) + for edp in edps: + edp.EndpointUrl = InternalServer._replace_inaddr_any(edp.EndpointUrl, netloc) + return edps @staticmethod def _get_netloc(params=None, sockname=None): @@ -188,11 +190,6 @@ def _get_netloc(params=None, sockname=None): netloc = sockname[0] + ":" + str(sockname[1]) return netloc - @staticmethod - def _endpoint_replace_inaddr_any(edp, netloc): - edp.EndpointUrl = InternalServer._replace_inaddr_any(edp.EndpointUrl, netloc) - return edp - @staticmethod def _replace_inaddr_any(urlStr, netloc): # If urlStr is '0.0.0.0:port' or '[::]:port', use netloc ip:port. @@ -201,18 +198,17 @@ def _replace_inaddr_any(urlStr, netloc): hostip = ip_address(parseResult.hostname) except ValueError: hostip = None - if not (hostip and netloc): + if not netloc: pass - elif ip_address(hostip) in (ip_address(INADDR_ANY), ip_address(IN6ADDR_ANY)): + elif hostip in (ip_address(INADDR_ANY), ip_address(IN6ADDR_ANY)): urlStr = parseResult._replace(netloc=netloc).geturl() return urlStr def find_servers(self, params): - servers = self._filter_servers(params) - servers = pickle.loads(pickle.dumps(servers)) + servers = deepcopy(self._filter_servers(params)) netloc = self._get_netloc(params) for srv in servers: - srv.DiscoveryUrls = [self._replace_inaddr_any(url, netloc) for url in srv.DiscoveryUrls] + srv.DiscoveryUrls = [self._replace_inaddr_any(dUrl, netloc) for dUrl in srv.DiscoveryUrls] return servers def _filter_servers(self, params):