diff --git a/examples/python/example.py b/examples/python/example.py index 7e0de34..e00ab35 100644 --- a/examples/python/example.py +++ b/examples/python/example.py @@ -66,7 +66,7 @@ print('serverPort = ', serverPort) # # Event handler # -class MyEventCallbackClass(libzt.PythonDirectorCallbackClass): +class MyEventCallbackClass(libzt.EventCallbackClass): def on_zerotier_event(self, msg): global is_online global is_joined @@ -95,12 +95,12 @@ class MyEventCallbackClass(libzt.PythonDirectorCallbackClass): # print("Starting ZeroTier..."); eventCallback = MyEventCallbackClass() -libzt.zts_start(keyPath, eventCallback, ztServicePort) +libzt.start(keyPath, eventCallback, ztServicePort) print("Waiting for node to come online...") while (not is_online): time.sleep(1) print("Joining network:", hex(networkId)); -libzt.zts_join(networkId) +libzt.join(networkId) while (not is_joined): time.sleep(1) # You can ping this app at this point print('Joined network') @@ -112,22 +112,23 @@ print('Joined network') # if (mode == 'server'): print("Starting server...") + serv = libzt.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) try: - serv = libzt.zerotier.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) + #serv.setblocking(True) serv.bind(('::', serverPort)) serv.listen(5) while True: conn, addr = serv.accept() print('Accepted connection from: ', addr) while True: - print('recv()...') + print('recv:') data = conn.recv(4096) if data: print('data = ', data) #print(type(b'what')) #exit(0) if not data: break - print('send()...') + print('send:') #bytes(data, 'ascii') + b'\x00' n_bytes = conn.send(data) # echo back to the server print('sent ' + str(n_bytes) + ' byte(s)') @@ -135,6 +136,7 @@ if (mode == 'server'): print('client disconnected') except Exception as e: print(e) + print('errno=',libzt.errno()) # See include/ZeroTierSockets.h for codes # @@ -142,17 +144,16 @@ if (mode == 'server'): # if (mode == 'client'): print("Starting client...") + client = libzt.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) try: - client = libzt.zerotier.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) print("connecting...") client.connect((remoteIP, serverPort)) - print("send...") + print("send:") data = 'Hello, world!' client.send(data) - print("rx...") data = client.recv(1024) - print('Received', repr(data)) except Exception as e: print(e) + print('errno=',libzt.errno()) diff --git a/include/ZeroTierSockets.h b/include/ZeroTierSockets.h index 917c943..0b25342 100644 --- a/include/ZeroTierSockets.h +++ b/include/ZeroTierSockets.h @@ -876,6 +876,8 @@ int zts_py_listen(int fd, int backlog); PyObject * zts_py_recv(int fd, int len, int flags); int zts_py_send(int fd, PyObject *buf, int len, int flags); int zts_py_close(int fd); +int zts_py_setblocking(int fd, int flag); +int zts_py_getblocking(int fd); #endif // ZTS_ENABLE_PYTHON diff --git a/pkg/pypi/MANIFEST.in b/pkg/pypi/MANIFEST.in index f57e51f..bfa060a 100644 --- a/pkg/pypi/MANIFEST.in +++ b/pkg/pypi/MANIFEST.in @@ -1,3 +1,3 @@ -README.rst +README.md setup.cfg setup.py diff --git a/pkg/pypi/libzt/__init__.py b/pkg/pypi/libzt/__init__.py index 3b8b2fe..9c0487c 100644 --- a/pkg/pypi/libzt/__init__.py +++ b/pkg/pypi/libzt/__init__.py @@ -1,4 +1,4 @@ __version__ = "1.3.3" from .libzt import * -from .prototype import ztsocket, zerotier \ No newline at end of file +from .sockets import * diff --git a/pkg/pypi/setup.cfg b/pkg/pypi/setup.cfg index 447a470..36b1ca0 100644 --- a/pkg/pypi/setup.cfg +++ b/pkg/pypi/setup.cfg @@ -1,4 +1,4 @@ [metadata] version = attr: libzt.__version__ -description-file = README.rst +description-file = README.md license_files = LICENSE \ No newline at end of file diff --git a/pkg/pypi/setup.py b/pkg/pypi/setup.py index 33155dc..2af77a6 100644 --- a/pkg/pypi/setup.py +++ b/pkg/pypi/setup.py @@ -4,6 +4,11 @@ from setuptools import setup, Extension, Command, Distribution import glob import os +from os import path +this_directory = path.abspath(path.dirname(__file__)) +with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + class BinaryDistribution(Distribution): def is_pure(self): return False @@ -67,16 +72,18 @@ setup( name = 'libzt', version = '1.3.3', description = 'ZeroTier', - long_description = 'Encrypted P2P communication between apps and services', +# long_description = 'Encrypted P2P communication between apps and services', + long_description=long_description, + long_description_content_type='text/markdown', author = 'ZeroTier, Inc.', - author_email = 'joseph.henry@zerotier.com', + author_email = 'joseph@zerotier.com', url = 'https://github.com/zerotier/libzt', license='BUSL 1.1', - download_url = 'https://github.com/zerotier/libzt/archive/1.3.3.tar.gz', - keywords = 'zerotier sdwan sdn virtual network socket p2p peer-to-peer', + download_url = 'https://github.com/zerotier/libzt/releases', + keywords = 'zerotier p2p peer-to-peer sdwan sdn virtual network socket tcp udp zt encryption encrypted', py_modules = ['libzt'], packages = ['libzt'], - classifiers = ['Development Status :: 3 - Alpha', + classifiers = ['Development Status :: 4 - Beta', 'Topic :: Internet', 'Topic :: System :: Networking', 'Topic :: Security :: Cryptography', @@ -88,13 +95,17 @@ setup( 'Intended Audience :: Telecommunications Industry', 'Intended Audience :: End Users/Desktop', 'License :: Free for non-commercial use', - 'Operating System :: MacOS :: MacOS X', + 'Operating System :: MacOS', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: BSD', + 'Operating System :: POSIX :: Linux', 'Operating System :: Unix', - 'Programming Language :: C++', 'Programming Language :: C', - 'Programming Language :: Python' + 'Programming Language :: C++', + 'Programming Language :: Python', + 'Programming Language :: Java', + 'Programming Language :: C#', + 'Programming Language :: Rust' ], distclass=BinaryDistribution, libraries=[cstuff], diff --git a/src/bindings/python/PythonSockets.cpp b/src/bindings/python/PythonSockets.cpp index 1741168..dc7aaf8 100644 --- a/src/bindings/python/PythonSockets.cpp +++ b/src/bindings/python/PythonSockets.cpp @@ -22,7 +22,25 @@ #ifdef ZTS_ENABLE_PYTHON -static int tuple_to_sockaddr(int family, +int zts_py_setblocking(int fd, int flag) +{ + int flags = ZTS_ERR_OK; + if ((flags = zts_fcntl(fd, F_GETFL, 0)) < 0) { + return ZTS_ERR_SOCKET; + } + return zts_fcntl(fd, F_SETFL, flags | ZTS_O_NONBLOCK); +} + +int zts_py_getblocking(int fd) +{ + int flags = ZTS_ERR_OK; + if ((flags = zts_fcntl(fd, F_GETFL, 0)) < 0) { + return ZTS_ERR_SOCKET; + } + return flags & ZTS_O_NONBLOCK; +} + +static int zts_py_tuple_to_sockaddr(int family, PyObject *addr_obj, struct zts_sockaddr *dst_addr, int *addrlen) { if (family == AF_INET) { @@ -33,7 +51,7 @@ static int tuple_to_sockaddr(int family, return ZTS_ERR_ARG; } if (!PyArg_ParseTuple(addr_obj, - "eti:tuple_to_sockaddr", "idna", &host_str, &port)) { + "eti:zts_py_tuple_to_sockaddr", "idna", &host_str, &port)) { return ZTS_ERR_ARG; } addr = (struct zts_sockaddr_in*)dst_addr; @@ -84,7 +102,7 @@ int zts_py_bind(int fd, int family, int type, PyObject *addr_obj) struct zts_sockaddr_storage addrbuf; int addrlen; int err; - if (tuple_to_sockaddr(family, addr_obj, + if (zts_py_tuple_to_sockaddr(family, addr_obj, (struct zts_sockaddr *)&addrbuf, &addrlen) != ZTS_ERR_OK) { return ZTS_ERR_ARG; @@ -101,7 +119,7 @@ int zts_py_connect(int fd, int family, int type, PyObject *addr_obj) struct zts_sockaddr_storage addrbuf; int addrlen; int err; - if (tuple_to_sockaddr(family, addr_obj, + if (zts_py_tuple_to_sockaddr(family, addr_obj, (struct zts_sockaddr *)&addrbuf, &addrlen) != ZTS_ERR_OK) { return ZTS_ERR_ARG; diff --git a/src/bindings/python/prototype.py b/src/bindings/python/prototype.py deleted file mode 100644 index bac71ff..0000000 --- a/src/bindings/python/prototype.py +++ /dev/null @@ -1,103 +0,0 @@ -import libzt - -import time -import struct -import pprint -pp = pprint.PrettyPrinter(width=41, compact=True) - -class zerotier(): - # Create a socket - def socket(sock_family, sock_type, sock_proto=0): - return ztsocket(sock_family, sock_type, sock_proto) - # Convert libzt error code to exception - def handle_error(err): - if (err == libzt.ZTS_ERR_SOCKET): - raise Exception('ZTS_ERR_SOCKET (' + str(err) + ')') - if (err == libzt.ZTS_ERR_SERVICE): - raise Exception('ZTS_ERR_SERVICE (' + str(err) + ')') - if (err == libzt.ZTS_ERR_ARG): - raise Exception('ZTS_ERR_ARG (' + str(err) + ')') - # ZTS_ERR_NO_RESULT isn't strictly an error - #if (err == libzt.ZTS_ERR_NO_RESULT): - # raise Exception('ZTS_ERR_NO_RESULT (' + err + ')') - if (err == libzt.ZTS_ERR_GENERAL): - raise Exception('ZTS_ERR_GENERAL (' + str(err) + ')') - -# ZeroTier pythonic low-level socket class -class ztsocket(): - - _fd = -1 # native layer file descriptor - _family = -1 - _type = -1 - _proto = -1 - _connected = False - _closed = True - _bound = False - - def __init__(self, sock_family=-1, sock_type=-1, sock_proto=-1, sock_fd=None): - self._fd = sock_fd - self._family = sock_family - self._type = sock_type - self._family = sock_family - # Only create native socket if no fd was provided. We may have - # accepted a connection - if (sock_fd == None): - self._fd = libzt.zts_socket(sock_family, sock_type, sock_proto) - - def has_dualstack_ipv6(): - return True - - @property - def family(self): - return _family - - @property - def type(self): - return _type - - # Bind the socket to a local interface address - def bind(self, local_address): - err = libzt.zts_py_bind(self._fd, self._family, self._type, local_address) - if (err < 0): - zerotier.handle_error(err) - - # Connect the socket to a remote address - def connect(self, remote_address): - err = libzt.zts_py_connect(self._fd, self._family, self._type, remote_address) - if (err < 0): - zerotier.handle_error(err) - - # Put the socket in a listening state (with an optional backlog argument) - def listen(self, backlog): - err = libzt.zts_py_listen(self._fd, backlog) - if (err < 0): - zerotier.handle_error(err) - - # Accept connection on the socket - def accept(self): - new_conn_fd, addr, port = libzt.zts_py_accept(self._fd) - if (new_conn_fd < 0): - zerotier.handle_error(acc_fd) - return None - return ztsocket(self._family, self._type, self._proto, new_conn_fd), addr - - # Read data from the socket - def recv(self, n_bytes, flags=0): - err, data = libzt.zts_py_recv(self._fd, n_bytes, flags) - if (err < 0): - zerotier.handle_error(err) - return None - return data - - # Write data to the socket - def send(self, data, flags=0): - err = libzt.zts_py_send(self._fd, data, len(data), flags) - if (err < 0): - zerotier.handle_error(err) - return err - - # Close the socket - def close(self): - err = libzt.zts_py_close(self._fd) - if (err < 0): - zerotier.handle_error(err) diff --git a/src/bindings/python/sockets.py b/src/bindings/python/sockets.py new file mode 100644 index 0000000..ac510fd --- /dev/null +++ b/src/bindings/python/sockets.py @@ -0,0 +1,347 @@ +import libzt + +class EventCallbackClass(libzt.PythonDirectorCallbackClass): + pass + +# Convert libzt error code to exception +def handle_error(err): + if (err == libzt.ZTS_ERR_SOCKET): + raise Exception('ZTS_ERR_SOCKET (' + str(err) + ')') + if (err == libzt.ZTS_ERR_SERVICE): + raise Exception('ZTS_ERR_SERVICE (' + str(err) + ')') + if (err == libzt.ZTS_ERR_ARG): + raise Exception('ZTS_ERR_ARG (' + str(err) + ')') + # ZTS_ERR_NO_RESULT isn't strictly an error + #if (err == libzt.ZTS_ERR_NO_RESULT): + # raise Exception('ZTS_ERR_NO_RESULT (' + err + ')') + if (err == libzt.ZTS_ERR_GENERAL): + raise Exception('ZTS_ERR_GENERAL (' + str(err) + ')') + +# This implementation of errno is NOT thread safe +# That is, this value is shared among all lower-level socket calls +# and may change for any reason at any time if you have multiple +# threads making socket calls. +def errno(): + return libzt.cvar.zts_errno + +# Start the ZeroTier service +def start(path, callback, port): + libzt.zts_start(path, callback, port) + +# Stop the ZeroTier service +def stop(): + libzt.zts_stop() + +# [debug] Restarts the ZeroTier service and network stack +def restart(): + libzt.zts_restart() + +# Permenantly shuts down the network stack. +def free(): + libzt.zts_free() + +# Join a ZeroTier network +def join(networkId): + libzt.zts_join(networkId) + +# Leave a ZeroTier network +def leave(networkId): + libzt.zts_leave(networkId) + +# Orbit a moon +def zts_orbit(moonWorldId, moonSeed): + return libzt.zts_orbit(moonWorldId, moonSeed) + +# De-orbit a moon +def zts_deorbit(moonWorldId): + return libzt.zts_deorbit(moonWorldId) + +# Pythonic class that wraps low-level sockets +class socket(): + + _fd = -1 # native layer file descriptor + _family = -1 + _type = -1 + _proto = -1 + _connected = False + _closed = True + _bound = False + + def __init__(self, sock_family=-1, sock_type=-1, sock_proto=-1, sock_fd=None): + self._fd = sock_fd + self._family = sock_family + self._type = sock_type + self._family = sock_family + # Only create native socket if no fd was provided. We may have + # accepted a connection + if (sock_fd == None): + self._fd = libzt.zts_socket(sock_family, sock_type, sock_proto) + + def has_dualstack_ipv6(): + return True + + @property + def family(self): + return _family + + @property + def type(self): + return _type + + @property + def proto(self): + return _proto + + # Intentionally not supported + def socketpair(self, family, type, proto): + raise NotImplementedError("socketpair(): libzt does not support AF_UNIX sockets") + + # Convenience function to create a connection to a remote host + def create_connection(self, remote_address): + # TODO: implement timeout + conn = socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) + conn.connect(remote_address) + return conn + + # Convenience function to create a listening socket + def create_server(self, local_address, family=libzt.ZTS_AF_INET, backlog=None): + # TODO: implement reuse_port + conn = socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) + conn.bind(local_address) + conn.listen(backlog) + return conn + + def fromfd(self, fd, family, type, proto=0): + raise NotImplementedError("fromfd(): Not supported. OS File descriptors aren't used in libzt.") + + def fromshare(self, data): + raise NotImplementedError("libzt does not support this (yet?)") + + def close(self, fd): + raise NotImplementedError("close(fd): Not supported OS File descriptors aren't used in libzt.") + + def getaddrinfo(self, host, port, family=0, type=0, proto=0, flags=0): + raise NotImplementedError("libzt does not support this (yet?)") + + def getfqdn(self, name): + raise NotImplementedError("libzt does not support this (yet?)") + + def gethostbyname(self, hostname): + raise NotImplementedError("libzt does not support this (yet?)") + + def gethostbyname_ex(self, hostname): + raise NotImplementedError("libzt does not support this (yet?)") + + def gethostname(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def gethostbyaddr(self, ip_address): + raise NotImplementedError("libzt does not support this (yet?)") + + def getnameinfo(self, sockaddr, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def getprotobyname(self, protocolname): + raise NotImplementedError("libzt does not support this (yet?)") + + def getservbyname(self, servicename, protocolname): + raise NotImplementedError("libzt does not support this (yet?)") + + def getservbyport(self, port, protocolname): + raise NotImplementedError("libzt does not support this (yet?)") + + def ntohl(x): + raise NotImplementedError("libzt does not support this (yet?)") + + def ntohs(x): + raise NotImplementedError("libzt does not support this (yet?)") + + def htonl(x): + raise NotImplementedError("libzt does not support this (yet?)") + + def htons(x): + raise NotImplementedError("libzt does not support this (yet?)") + + def inet_aton(ip_string): + raise NotImplementedError("libzt does not support this (yet?)") + + def inet_ntoa(packed_ip): + raise NotImplementedError("libzt does not support this (yet?)") + + def inet_pton(address_family, ip_string): + raise NotImplementedError("libzt does not support this (yet?)") + + def inet_ntop(address_family, packed_ip): + raise NotImplementedError("libzt does not support this (yet?)") + + def CMSG_LEN(length): + raise NotImplementedError("libzt does not support this (yet?)") + + def CMSG_SPACE(length): + raise NotImplementedError("libzt does not support this (yet?)") + + def getdefaulttimeout(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def setdefaulttimeout(self, timeout): + raise NotImplementedError("libzt does not support this (yet?)") + + def sethostname(self, name): + raise NotImplementedError("libzt does not support this (yet?)") + + def if_nameindex(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def if_nametoindex(self, if_name): + raise NotImplementedError("if_nametoindex(): libzt does not name interfaces.") + + def if_indextoname(self, if_index): + raise NotImplementedError("if_indextoname(): libzt does not name interfaces.") + + # Accept connection on the socket + def accept(self): + new_conn_fd, addr, port = libzt.zts_py_accept(self._fd) + if (new_conn_fd < 0): + handle_error(new_conn_fd) + return None + return ztsocket(self._family, self._type, self._proto, new_conn_fd), addr + + # Bind the socket to a local interface address + def bind(self, local_address): + err = libzt.zts_py_bind(self._fd, self._family, self._type, local_address) + if (err < 0): + handle_error(err) + + # Close the socket + def close(self): + err = libzt.zts_py_close(self._fd) + if (err < 0): + handle_error(err) + + # Connect the socket to a remote address + def connect(self, remote_address): + err = libzt.zts_py_connect(self._fd, self._family, self._type, remote_address) + if (err < 0): + handle_error(err) + + # Connect to remote host but return low-level result code, and errno on failure + # This uses a non-thread-safe implementation of errno + def connect_ex(self, remote_address): + err = libzt.zts_py_connect(self._fd, self._family, self._type, remote_address) + if (err < 0): + return errno() + return err + + def detach(self): + raise NotImplementedError("detach(): Not supported. OS File descriptors aren't used in libzt.") + + def dup(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def fileno(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def get_inheritable(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def getpeername(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def getsockname(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def getsockopt(self, level, optname, buflen): + raise NotImplementedError("libzt does not support this (yet?)") + + # Get whether this socket is in blocking or non-blocking mode + def getblocking(self): + return libzt.zts_py_getblocking(self._fd) + + def gettimeout(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def ioctl(self, control, option): + raise NotImplementedError("libzt does not support this (yet?)") + + # Put the socket in a listening state (with an optional backlog argument) + def listen(self, backlog): + err = libzt.zts_py_listen(self._fd, backlog) + if (err < 0): + handle_error(err) + + def makefile(mode='r', buffering=None, *, encoding=None, errors=None, newline=None): + raise NotImplementedError("libzt does not support this (yet?)") + + # Read data from the socket + def recv(self, n_bytes, flags=0): + err, data = libzt.zts_py_recv(self._fd, n_bytes, flags) + if (err < 0): + handle_error(err) + return None + return data + + def recvfrom(self, bufsize, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def recvmsg(self, bufsize, ancbufsize, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def recvmsg_into(self, buffers, ancbufsize, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def recvfrom_into(self, buffer, nbytes, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def recv_into(self, buffer, nbytes, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + # Write data to the socket + def send(self, data, flags=0): + err = libzt.zts_py_send(self._fd, data, len(data), flags) + if (err < 0): + handle_error(err) + return err + + def sendall(self, bytes, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def sendto(self, bytes, address): + raise NotImplementedError("libzt does not support this (yet?)") + + def sendto(self, bytes, flags, address): + raise NotImplementedError("libzt does not support this (yet?)") + + def sendmsg(self, buffers, ancdata, flags, address): + raise NotImplementedError("libzt does not support this (yet?)") + + def sendmsg_afalg(self, msg, *, op, iv, assoclen, flags): + raise NotImplementedError("sendmsg_afalg(): libzt does not support AF_ALG") + + def send_fds(self, sock, buffers, fds, flags, address): + raise NotImplementedError("libzt does not support this (yet?)") + + def recv_fds(self, sock, bufsize, maxfds, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def sendfile(self, file, offset=0, count=None): + raise NotImplementedError("libzt does not support this (yet?)") + + def set_inheritable(self, inheritable): + raise NotImplementedError("libzt does not support this (yet?)") + + # Set whether this socket is in blocking or non-blocking mode + def setblocking(self, flag): + libzt.zts_py_setblocking(self._fd, flag) + + def settimeout(self, value): + raise NotImplementedError("libzt does not support this (yet?)") + + def setsockopt(self, level, optname, value): + # TODO: value: buffer + # TODO: value: int + # TODO: value: None -> optlen required + raise NotImplementedError("libzt does not support this (yet?)") + + # Shut down one or more aspects (rx/tx) of the socket + def shutdown(self, how): + libzt.shutdown(self._fd, how)