From 943065832145fd3bed1a92f5b6763774f097a723 Mon Sep 17 00:00:00 2001 From: Eugene K Date: Fri, 14 Oct 2022 08:18:30 -0400 Subject: [PATCH] make ziti_getaddrinfo() conform to standard getaddrinfo and return assigned IP --- setup.cfg | 2 +- src/openziti/zitilib.py | 116 +++++++++++++++++++++++++++++++++++++++ src/openziti/zitisock.py | 11 ++-- tests/ziti_tests.py | 15 +++++ 4 files changed, 137 insertions(+), 7 deletions(-) diff --git a/setup.cfg b/setup.cfg index dc478c5..e3b6eda 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,4 +38,4 @@ tag_prefix = v parentdir_prefix = openziti- [openziti] -ziti_sdk_version = 0.30.0 \ No newline at end of file +ziti_sdk_version = 0.30.1 \ No newline at end of file diff --git a/src/openziti/zitilib.py b/src/openziti/zitilib.py index 2e38274..802ccb9 100644 --- a/src/openziti/zitilib.py +++ b/src/openziti/zitilib.py @@ -15,6 +15,7 @@ import ctypes import os import platform +import socket from typing import Tuple _mod_path = os.path.dirname(__file__) @@ -38,6 +39,78 @@ class _Ver(ctypes.Structure): def __repr__(self): return f'({self.version}, {self.revision})' +class SockAddrIn(ctypes.Structure): + """ + maps struct sockaddr_in + + NOTE: on Linux/Win32 the first two bytes are address family as short + on Darwin the first two bytes + """ + _fields_ = [ + ('_family', ctypes.c_uint8 * 2), + ('_port', ctypes.c_int16), + ('_addr', ctypes.c_uint8 * 4) + ] + + def ip(self): + return '.'.join(str(o) for o in self._addr) + + def af(self): + fam = self._family[0] + if osname == 'darwin': + fam = self._family[1] + return socket.AddressFamily(fam) + + @property + def port(self): + return socket.ntohs(self._port) + def __repr__(self): + return f'{self.af().name}:{self.ip()}:{self.port}' + + +class _AddrInfo(ctypes.Structure): + """ + int ai_flags; /* Input flags. */ + int ai_family; /* Protocol family for socket. */ + int ai_socktype; /* Socket type. */ + int ai_protocol; /* Protocol for socket. */ + socklen_t ai_addrlen; /* Length of socket address. */ + struct sockaddr *ai_addr; /* Socket address for socket. */ + char *ai_canonname; /* Canonical name for service location. */ + struct addrinfo *ai_next; /* Pointer to next in list. */ + + NOTE: the order of ai_addr and ai_canonname is switched on Dorwin and Windows compared to Linux + """ + _fields_ = [ + ('ai_flags', ctypes.c_int), + ('ai_family', ctypes.c_int), + ('ai_socktype', ctypes.c_int), + ('ai_protocol', ctypes.c_int), + ('ai_addrlen', ctypes.c_int32), + ('ai_p1', ctypes.c_void_p), + ('ai_p2', ctypes.c_void_p), + ('ai_next', ctypes.c_void_p) + ] + + def get_addr(self): + addr_p = self.ai_p2 + if osname == 'linux': + addr_p = self.ai_p1 + if addr_p is None: + return None + return SockAddrIn.from_address(addr_p) + + def get_canonname(self): + p = self.ai_p1 + if osname == 'linux': + p = self.ai_p2 + if p is None: + return None + return str(ctypes.cast(p, ctypes.c_char_p)) + + def __repr__(self): + return '|'.join((x[0] + '=' + str(getattr(self,x[0]))) for x in _AddrInfo._fields_) + _ziti_version = ziti.ziti_get_version _ziti_version.restype = ctypes.POINTER(_Ver) @@ -90,6 +163,14 @@ def __repr__(self): ] _ziti_enroll.restype = ctypes.c_int +_ziti_resolve = ziti.Ziti_resolve +_ziti_resolve.argtypes = [ + ctypes.c_char_p, + ctypes.c_char_p, + ctypes.POINTER(_AddrInfo), + ctypes.c_void_p +] + def free_win32(arg): pass @@ -190,3 +271,38 @@ def enroll(jwt, key=None, cert=None): return id_json.value.decode() finally: _free(id_json) + + +def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0): + if not isinstance(host, bytes): + host = bytes(str(host), 'utf-8') + if not isinstance(port, bytes): + port = bytes(str(port), 'utf-8') + + hints = _AddrInfo(ai_family=family, ai_socktype=type, ai_protocol=proto, ai_flags=flags) + addr_p = ctypes.c_void_p() + rc = _ziti_resolve(host, port, hints, ctypes.byref(addr_p)) + + if rc != 0: + return None + + addr_p = addr_p.value + result = [] + while addr_p: + addr = _AddrInfo.from_address(addr_p) + addr_in = addr.get_addr() + try: + af = socket.AddressFamily(addr.ai_family) + except: + af = addr.ai_family + try: + t = socket.SocketKind(addr.ai_socktype) + except: + t = addr.ai_socktype + + a = (af, t, addr.ai_protocol, addr.get_canonname(), (addr_in.ip(), addr_in.port)) + result.append(a) + addr_p = addr.ai_next + + + return result \ No newline at end of file diff --git a/src/openziti/zitisock.py b/src/openziti/zitisock.py index d029d70..e5ea733 100644 --- a/src/openziti/zitisock.py +++ b/src/openziti/zitisock.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import socket +from socket import getaddrinfo as PyGetaddrinfo from socket import socket as PySocket from typing import Tuple @@ -97,10 +98,8 @@ def create_ziti_connection(address, **_): sock.connect(address) return sock - def ziti_getaddrinfo(host, port, family=0, type=0, proto=0, flags=0): - # pylint: disable=unused-argument,redefined-builtin - # pylint: disable=protected-access,no-member - return [(socket._intenum_converter(socket.AF_INET, socket.AddressFamily), - socket._intenum_converter(type, socket.SocketKind), - proto, '', (host, port))] + addrs = zitilib.getaddrinfo(host, port, family, type, proto, flags) + if addrs is None: + addrs = PyGetaddrinfo(host, port, family, type, proto, flags) + return addrs diff --git a/tests/ziti_tests.py b/tests/ziti_tests.py index 896bcaf..13c8d13 100644 --- a/tests/ziti_tests.py +++ b/tests/ziti_tests.py @@ -39,3 +39,18 @@ def test_monkeypatch(self): with self.assertRaises(ConnectionError): get_httpbin('http://httpbin.ziti/json') + def test_resolve(selfs): + with openziti.monkeypatch(): + import socket + addrlist = socket.getaddrinfo(host='httpbin.ziti', port=80, type=socket.SOCK_STREAM) + assert len(addrlist) == 1 + af, socktype, proto, name, addr = addrlist[0] + assert af == socket.AF_INET + assert socktype == socket.SOCK_STREAM + assert proto == socket.IPPROTO_TCP + assert isinstance(addr, tuple) + assert isinstance(addr[0], str) + assert isinstance(addr[1], int) + assert addr[1] == 80 + assert addr[0].startswith('100.64.0.') +