Update Python bindings to 1.4.0 API (WIP)

This commit is contained in:
Joseph Henry
2021-05-02 21:30:21 -07:00
parent 6f42338f6e
commit 6a77f0092f
19 changed files with 619 additions and 389 deletions

View File

@@ -9,8 +9,11 @@ jobs:
- name: Checkout repo
uses: actions/checkout@v2
- name: Install black
run: pip3 install black
- name: Format code
run: ./build.sh format-code
run: ./build.sh format-code "all"
- name: Commit changes
uses: joseph-henry/add-and-commit@v7

View File

@@ -4,6 +4,13 @@
# | SYSTEM DISCOVERY AND CONFIGURATION |
# -----------------------------------------------------------------------------
CLANG_FORMAT=clang-format-11
PYTHON=python3
PIP=pip3
libzt=$(pwd)
# Find and set cmake
CMAKE=cmake3
if [[ $(which $CMAKE) = "" ]];
@@ -356,7 +363,7 @@ host-uninstall()
# └── pkg
# └── libzt-1.3.4b1-cp39-cp39-macosx_11_0_x86_64.whl
#
host-python-wheel()
host-python()
{
ARTIFACT="python"
# Default to release
@@ -365,30 +372,47 @@ host-python-wheel()
TARGET_BUILD_DIR=$DEFAULT_HOST_BIN_OUTPUT_DIR-$ARTIFACT-$BUILD_TYPE
PKG_OUTPUT_DIR=$TARGET_BUILD_DIR/pkg
mkdir -p $PKG_OUTPUT_DIR
# Generate new wrapper
#swig -c++ -python -o src/bindings/python/zt_wrap.cxx -Iinclude src/bindings/python/zt.i
# Requires setuptools, etc
cd pkg/pypi && ./build.sh wheel && cp -f dist/*.whl $PKG_OUTPUT_DIR
echo -e "\nFinished wheel:\n"
echo $PKG_OUTPUT_DIR/*.whl
# Test Python wheel
if [[ $2 = *"test"* ]]; then
if [[ -z "${alice_path}" ]]; then
echo "Please set necessary environment variables for test"
exit 0
fi
pip3 uninstall -y libzt
pip3 install $PKG_OUTPUT_DIR/*.whl
cd $libzt
$PYTHON test/selftest.py server $alice_path $testnet $port4 &
$PYTHON test/selftest.py client $bob_path $testnet $alice_ip4 $port4 &
fi
}
# Build shared library with python wrapper symbols exported
host-python()
{
ARTIFACT="python"
# Default to release
BUILD_TYPE=${1:-release}
VARIANT="-DZTS_ENABLE_PYTHON=True"
CACHE_DIR=$DEFAULT_HOST_BUILD_CACHE_DIR-$ARTIFACT-$BUILD_TYPE
TARGET_BUILD_DIR=$DEFAULT_HOST_BIN_OUTPUT_DIR-$ARTIFACT-$BUILD_TYPE
LIB_OUTPUT_DIR=$TARGET_BUILD_DIR/lib
rm -rf $LIB_OUTPUT_DIR
mkdir -p $LIB_OUTPUT_DIR
# Optional step to generate new SWIG wrapper
swig -c++ -python -o src/bindings/python/zt_wrap.cxx -Iinclude src/bindings/python/zt.i
$CMAKE $VARIANT -H. -B$CACHE_DIR -DCMAKE_BUILD_TYPE=$BUILD_TYPE
$CMAKE --build $CACHE_DIR $BUILD_CONCURRENCY
cp -f $CACHE_DIR/lib/$SHARED_LIB_NAME $LIB_OUTPUT_DIR/_libzt.so
echo -e "\n - Build cache : $CACHE_DIR\n - Build output : $BUILD_OUTPUT_DIR\n"
$TREE $TARGET_BUILD_DIR
}
#host-python()
#{
# ARTIFACT="python"
# # Default to release
# BUILD_TYPE=${1:-release}
# VARIANT="-DZTS_ENABLE_PYTHON=True"
# CACHE_DIR=$DEFAULT_HOST_BUILD_CACHE_DIR-$ARTIFACT-$BUILD_TYPE
# TARGET_BUILD_DIR=$DEFAULT_HOST_BIN_OUTPUT_DIR-$ARTIFACT-$BUILD_TYPE
# LIB_OUTPUT_DIR=$TARGET_BUILD_DIR/lib
# rm -rf $LIB_OUTPUT_DIR
# mkdir -p $LIB_OUTPUT_DIR
# # Optional step to generate new SWIG wrapper
# swig -c++ -python -o src/bindings/python/zt_wrap.cxx -Iinclude src/bindings/python/zt.i
# $CMAKE $VARIANT -H. -B$CACHE_DIR -DCMAKE_BUILD_TYPE=$BUILD_TYPE
# $CMAKE --build $CACHE_DIR $BUILD_CONCURRENCY
# cp -f $CACHE_DIR/lib/$SHARED_LIB_NAME $LIB_OUTPUT_DIR/_libzt.so
# echo -e "\n - Build cache : $CACHE_DIR\n - Build output : $BUILD_OUTPUT_DIR\n"
# $TREE $TARGET_BUILD_DIR
#}
# Build shared library with P/INVOKE wrapper symbols exported
host-pinvoke()
@@ -579,10 +603,17 @@ test()
format-code()
{
if [[ ! $(which clang-format) = "" ]];
if [[ $1 = *"all"* ]]; then
format-code "clang"
format-code "python"
fi
# Clang-format
if [[ $1 = *"clang"* ]]; then
if [[ ! $(which $CLANG_FORMAT) = "" ]];
then
# Eventually: find . -path ./ext -prune -false -o -type f \( -iname \*.c -o -iname \*.h -o -iname \*.cpp -o -iname \*.hpp \) -exec clang-format -i {} \;
clang-format-11 -i include/*.h \
$CLANG_FORMAT -i include/*.h \
src/*.c \
src/*.cpp \
src/*.hpp \
@@ -595,10 +626,23 @@ format-code()
src/bindings/csharp/*.cxx \
src/bindings/java/*.java \
src/bindings/java/*.cxx \
examples/csharp/*.cs
examples/csharp/*.cs \
src/bindings/python/*.cxx \
src/bindings/python/*.h
return 0
else
echo "Please install clang-format."
echo "Please install clang-format"
fi
fi
# Python
if [[ $1 = *"python"* ]]; then
if [[ ! $($PIP list | grep black) = "" ]];
then
$PYTHON -m black src/bindings/python/*.py
$PYTHON -m black examples/python/*.py
else
echo "Please install python module (black)"
fi
fi
}
@@ -637,6 +681,8 @@ clean()
rm -rf $ANDROID_PKG_PROJ_DIR/app/build
rm -rf $ANDROID_PKG_PROJ_DIR/app/src/main/java/com/zerotier/libzt/*.java
rm -rf $ANDROID_PKG_PROJ_DIR/app/.externalNativeBuild
# Python pkg
cd pkg/pypi && ./build.sh clean
# Remove whatever remains
find . \
\( -name '*.dylib' \

View File

@@ -1,106 +1,92 @@
'''Example low-level socket usage'''
"""Example low-level socket usage"""
import time
import sys
import libzt
def print_usage():
'''print help'''
"""print help"""
print(
"\nUsage: <server|client> <id_path> <nwid> <zt_service_port> <remote_ip> <remote_port>\n"
"\nUsage: <server|client> <storage_path> <net_id> <remote_ip> <remote_port>\n"
)
print("Ex: python3 demo.py server . 0123456789abcdef 9994 8080")
print("Ex: python3 demo.py client . 0123456789abcdef 9994 192.168.22.1 8080\n")
if len(sys.argv) < 6:
print("Ex: python3 example.py server . 0123456789abcdef 8080")
print("Ex: python3 example.py client . 0123456789abcdef 192.168.22.1 8080\n")
if len(sys.argv) < 5:
print("Too few arguments")
if len(sys.argv) > 7:
if len(sys.argv) > 6:
print("Too many arguments")
sys.exit(0)
is_joined = False # Flags to keep state
is_online = False # Flags to keep state
#
# (Optional) Event handler
#
def on_zerotier_event(event_code, id):
if event_code == libzt.ZTS_EVENT_NODE_ONLINE:
print("ZTS_EVENT_NODE_ONLINE (" + str(event_code) + ") : " + hex(id))
if event_code == libzt.ZTS_EVENT_NODE_OFFLINE:
print("ZTS_EVENT_NODE_OFFLINE (" + str(event_code) + ") : " + hex(id))
if event_code == libzt.ZTS_EVENT_NETWORK_READY_IP4:
print("ZTS_EVENT_NETWORK_READY_IP4 (" + str(event_code) + ") : " + hex(id))
if event_code == libzt.ZTS_EVENT_NETWORK_READY_IP6:
print("ZTS_EVENT_NETWORK_READY_IP6 (" + str(event_code) + ") : " + hex(id))
if event_code == libzt.ZTS_EVENT_PEER_DIRECT:
print("ZTS_EVENT_PEER_DIRECT (" + str(event_code) + ") : " + hex(id))
if event_code == libzt.ZTS_EVENT_PEER_RELAY:
print("ZTS_EVENT_PEER_RELAY (" + str(event_code) + ") : " + hex(id))
#
# Event handler
#
class MyEventCallbackClass(libzt.EventCallbackClass):
def on_zerotier_event(self, msg):
global is_online
global is_joined
print("eventCode=", msg.eventCode)
if msg.eventCode == libzt.ZTS_EVENT_NODE_ONLINE:
print("ZTS_EVENT_NODE_ONLINE")
print("nodeId=" + hex(msg.node.address))
# The node is now online, you can join/leave networks
is_online = True
if msg.eventCode == libzt.ZTS_EVENT_NODE_OFFLINE:
print("ZTS_EVENT_NODE_OFFLINE")
if msg.eventCode == libzt.ZTS_EVENT_NETWORK_READY_IP4:
print("ZTS_EVENT_NETWORK_READY_IP4")
is_joined = True
# The node has successfully joined a network and has an address
# you can perform network calls now
if msg.eventCode == libzt.ZTS_EVENT_PEER_DIRECT:
print("ZTS_EVENT_PEER_DIRECT")
if msg.eventCode == libzt.ZTS_EVENT_PEER_RELAY:
print("ZTS_EVENT_PEER_RELAY")
#
# Main
#
def main():
global is_online
global is_joined
key_file_path = "." # Where identity files are stored
network_id = 0 # Network to join
# Port used by ZeroTier to send encpryted UDP traffic
# NOTE: Should be different from other instances of ZeroTier
# running on the same machine
zt_service_port = 9997
mode = None # client|server
storage_path = "." # Where identity files are stored
net_id = 0 # Network to join
remote_ip = None # ZeroTier IP of remote node
remote_port = 8080 # ZeroTier port your app logic may use
mode = None # client|server
if len(sys.argv) < 6 or len(sys.argv) > 7:
if len(sys.argv) < 5 or len(sys.argv) > 6:
print_usage()
if sys.argv[1] == "server" and len(sys.argv) == 6:
if sys.argv[1] == "server" and len(sys.argv) == 5:
mode = sys.argv[1]
key_file_path = sys.argv[2]
network_id = int(sys.argv[3], 16)
zt_service_port = int(sys.argv[4])
storage_path = sys.argv[2]
net_id = int(sys.argv[3], 16)
remote_port = int(sys.argv[4])
if sys.argv[1] == "client" and len(sys.argv) == 6:
mode = sys.argv[1]
storage_path = sys.argv[2]
net_id = int(sys.argv[3], 16)
remote_ip = sys.argv[4]
remote_port = int(sys.argv[5])
if sys.argv[1] == "client" and len(sys.argv) == 7:
mode = sys.argv[1]
key_file_path = sys.argv[2]
network_id = int(sys.argv[3], 16)
zt_service_port = int(sys.argv[4])
remote_ip = sys.argv[5]
remote_port = int(sys.argv[6])
if mode is None:
print_usage()
print("mode = ", mode)
print("path = ", key_file_path)
print("network_id = ", network_id)
print("zt_service_port = ", zt_service_port)
print("storage_path = ", storage_path)
print("net_id = ", net_id)
print("remote_ip = ", remote_ip)
print("remote_port = ", remote_port)
#
# Example start and join logic
# Node initialization and start
#
print("Starting ZeroTier...")
event_callback = MyEventCallbackClass()
libzt.start(key_file_path, event_callback, zt_service_port)
n = libzt.ZeroTierNode()
n.init_set_event_handler(on_zerotier_event) # Optional
n.init_from_storage(storage_path) # Optional
n.init_set_port(9994) # Optional
n.node_start()
print("Waiting for node to come online...")
while not is_online:
while not n.node_is_online():
time.sleep(1)
print("Joining network:", hex(net_id))
n.net_join(net_id)
while not n.net_transport_is_ready(net_id):
time.sleep(1)
print("Joining network:", hex(network_id))
libzt.join(network_id)
while not is_joined:
time.sleep(1) # You can ping this app at this point
print("Joined network")
#
@@ -140,7 +126,7 @@ def main():
try:
print("connecting...")
client.connect((remote_ip, remote_port))
data = "Hello, world!"
data = "Hello, roots!"
print("send: ", data)
sent_bytes = client.send(data)
print("sent: " + str(sent_bytes) + " byte(s)")
@@ -150,5 +136,6 @@ def main():
print(ex)
print("errno=", libzt.errno())
if __name__ == "__main__":
main()

View File

@@ -1058,6 +1058,10 @@ int zts_py_getblocking(int fd);
// Central API //
//----------------------------------------------------------------------------//
#ifdef ZTS_ENABLE_PYTHON
#define ZTS_DISABLE_CENTRAL_API 1
#endif
#ifndef ZTS_DISABLE_CENTRAL_API
#define ZTS_CENTRAL_DEFAULT_URL "https://my.zerotier.com"

View File

@@ -1,3 +0,0 @@
README.md
setup.cfg
setup.py

View File

@@ -8,7 +8,7 @@ ext()
# Symbolic link to source tree so that sdist structure makes sense
ln -s ../../ native
# Re-build wrapper to export C symbols
swig -c++ -python -o native/src/bindings/python/zt_wrap.cpp -I./native/include native/src/bindings/python/zt.i
swig -c++ -python -o native/src/bindings/python/zt_wrap.cxx -I./native/include native/src/bindings/python/zt.i
# Copy language bindings into module directory
cp -f native/src/bindings/python/*.py libzt/
cp -f native/LICENSE.txt LICENSE
@@ -30,6 +30,7 @@ clean()
find . -name '__pycache__' -type d -delete
rm -rf libzt/sockets.py
rm -rf libzt/libzt.py
rm -rf libzt/node.py
rm -rf src ext build dist native
rm -rf libzt.egg-info
rm -rf LICENSE

View File

@@ -1,3 +1,4 @@
from .libzt import *
from .sockets import *
from .node import *
from .version import __version__

View File

@@ -1 +1 @@
__version__ = "1.3.4b1"
__version__ = "1.4.0a0"

View File

@@ -1,6 +1,6 @@
[metadata]
version = attr: libzt.__version__
description-file = README.md
description_file = README.md
license_files =
native/LICENSE.txt
native/ext/THIRDPARTY.txt

View File

@@ -19,6 +19,27 @@ class BinaryDistribution(Distribution):
def is_pure(self):
return False
# monkey-patch for parallel compilation
# Copied from: https://stackoverflow.com/a/13176803
def parallelCCompile(self, sources, output_dir=None, macros=None, include_dirs=None, debug=0, extra_preargs=None, extra_postargs=None, depends=None):
# those lines are copied from distutils.ccompiler.CCompiler directly
macros, objects, extra_postargs, pp_opts, build = self._setup_compile(output_dir, macros, include_dirs, sources, depends, extra_postargs)
cc_args = self._get_cc_args(pp_opts, debug, extra_preargs)
# parallel code
N=16 # number of parallel compilations
import multiprocessing.pool
def _single_compile(obj):
try: src, ext = build[obj]
except KeyError: return
self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
# convert to list, imap is evaluated on-demand
list(multiprocessing.pool.ThreadPool(N).imap(_single_compile,objects))
return objects
import distutils.ccompiler
distutils.ccompiler.CCompiler.compile=parallelCCompile
# Build
cpp_glob = []
c_glob = []
@@ -30,7 +51,7 @@ if os.name == 'nt':
# Everything else
else:
cpp_glob.extend(list(glob.glob('native/src/bindings/python/*.cpp')))
cpp_glob.extend(list(glob.glob('native/src/bindings/python/*.cxx')))
cpp_glob.extend(list(glob.glob('native/src/*.cpp')))
cpp_glob.extend(list(glob.glob('native/ext/ZeroTierOne/node/*.cpp')))
cpp_glob.extend(list(glob.glob('native/ext/ZeroTierOne/osdep/OSUtils.cpp')))
@@ -44,13 +65,14 @@ else:
'native/ext/lwip/src/include',
'native/ext/lwip-contrib/ports/unix/port/include',
'native/ext/ZeroTierOne/include',
'native/ext/ZeroTierOne',
'native/ext/ZeroTierOne/node',
'native/ext/ZeroTierOne/service',
'native/ext/ZeroTierOne/osdep',
'native/ext/ZeroTierOne/controller']
libzt_module = Extension('libzt._libzt',
extra_compile_args=['-std=c++11', '-DZTS_ENABLE_PYTHON=1', '-DZT_SDK'],
extra_compile_args=['-std=c++11', '-DZTS_ENABLE_PYTHON=1', '-DZT_SDK', '-Wno-parentheses-equality', '-Wno-macro-redefined', '-Wno-tautological-overlap-compare', '-Wno-tautological-constant-out-of-range-compare'],
sources=cpp_glob, include_dirs=my_include_dirs)
# Separate C library, this is needed since C++ compiler flags are applied

View File

@@ -11,12 +11,13 @@
*/
/****/
#include "ZeroTierSockets.h"
#ifndef ZTS_DISABLE_CENTRAL_API
#include "Debug.hpp"
#include "Mutex.hpp"
#include "OSUtils.hpp"
#include "ZeroTierSockets.h"
#include <cstdint>
#include <cstring>

View File

@@ -19,6 +19,7 @@
#include "Events.hpp"
#include "NodeService.hpp"
#include "Signals.hpp"
#include "VirtualTap.hpp"
#include <string.h>
@@ -65,7 +66,7 @@ int init_subsystems()
return ZTS_ERR_SERVICE;
}
#ifdef ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS
_install_signal_handlers();
zts_install_signal_handlers();
#endif // ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS
if (! zts_service) {
zts_service = new NodeService();

View File

@@ -36,7 +36,7 @@
#include <execinfo.h>
#include <signal.h>
void _signal_handler(int signal)
void zts_signal_handler(int signal)
{
/*
switch(signal)
@@ -65,15 +65,15 @@ void _signal_handler(int signal)
exit(signal);
}
void _install_signal_handlers()
void zts_install_signal_handlers()
{
signal(SIGINT, &_signal_handler);
signal(SIGINT, &zts_signal_handler);
/*
signal(SIGABRT, &_signal_handler);
signal(SIGFPE, &_signal_handler);
signal(SIGILL, &_signal_handler);
signal(SIGSEGV, &_signal_handler);
signal(SIGTERM, &_signal_handler);
signal(SIGABRT, &zts_signal_handler);
signal(SIGFPE, &zts_signal_handler);
signal(SIGILL, &zts_signal_handler);
signal(SIGSEGV, &zts_signal_handler);
signal(SIGTERM, &zts_signal_handler);
*/
}

View File

@@ -26,9 +26,9 @@
#ifdef ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS
void _signal_handler(int signal);
void zts_signal_handler(int signal);
void _install_signal_handlers();
void zts_install_signal_handlers();
#endif // ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS

View File

@@ -1,218 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2025-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
/**
* @file
*
* ZeroTier Socket API (Python)
*/
#include <string.h>
#include "lwip/sockets.h"
#include "lwip/inet.h"
#include "ZeroTierSockets.h"
#ifdef ZTS_ENABLE_PYTHON
int zts_py_setblocking(int fd, int block)
{
int new_flags, cur_flags, err = 0;
Py_BEGIN_ALLOW_THREADS
cur_flags = zts_fcntl(fd, F_GETFL, 0);
if (cur_flags < 0) {
err = ZTS_ERR_SOCKET;
goto done;
}
if (!block) {
new_flags |= ZTS_O_NONBLOCK;
} else {
new_flags &= ~ZTS_O_NONBLOCK;
}
if (new_flags != cur_flags) {
err = zts_fcntl(fd, F_SETFL, new_flags);
}
done:
Py_END_ALLOW_THREADS
return err;
}
int zts_py_getblocking(int fd)
{
int flags;
Py_BEGIN_ALLOW_THREADS
flags = zts_fcntl(fd, F_GETFL, 0);
Py_END_ALLOW_THREADS
if (flags < 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) {
struct zts_sockaddr_in* addr;
char *host_str;
int result, port;
if (!PyTuple_Check(addr_obj)) {
return ZTS_ERR_ARG;
}
if (!PyArg_ParseTuple(addr_obj,
"eti:zts_py_tuple_to_sockaddr", "idna", &host_str, &port)) {
return ZTS_ERR_ARG;
}
addr = (struct zts_sockaddr_in*)dst_addr;
zts_inet_pton(ZTS_AF_INET, host_str, &(addr->sin_addr.s_addr));
PyMem_Free(host_str);
if (port < 0 || port > 0xFFFF) {
return ZTS_ERR_ARG;
}
if (result < 0) {
return ZTS_ERR_ARG;
}
addr->sin_family = AF_INET;
addr->sin_port = lwip_htons((short)port);
*addrlen = sizeof *addr;
return ZTS_ERR_OK;
}
if (family == AF_INET6) {
// TODO
}
return ZTS_ERR_ARG;
}
PyObject * zts_py_accept(int fd)
{
struct zts_sockaddr_in addrbuf;
socklen_t addrlen = sizeof(addrbuf);
memset(&addrbuf, 0, addrlen);
int err = zts_accept(fd, (struct zts_sockaddr*)&addrbuf, &addrlen);
char ipstr[ZTS_INET_ADDRSTRLEN];
memset(ipstr, 0, sizeof(ipstr));
zts_inet_ntop(ZTS_AF_INET, &(addrbuf.sin_addr), ipstr, ZTS_INET_ADDRSTRLEN);
PyObject *t;
t = PyTuple_New(3);
PyTuple_SetItem(t, 0, PyLong_FromLong(err)); // New file descriptor
PyTuple_SetItem(t, 1, PyUnicode_FromString(ipstr));
PyTuple_SetItem(t, 2, PyLong_FromLong(lwip_ntohs(addrbuf.sin_port)));
Py_INCREF(t);
return t;
}
int zts_py_listen(int fd, int backlog)
{
if (backlog < 0) {
backlog = 128;
}
return zts_listen(fd, backlog);
}
int zts_py_bind(int fd, int family, int type, PyObject *addr_obj)
{
struct zts_sockaddr_storage addrbuf;
int addrlen;
int err;
if (zts_py_tuple_to_sockaddr(family, addr_obj,
(struct zts_sockaddr *)&addrbuf, &addrlen) != ZTS_ERR_OK)
{
return ZTS_ERR_ARG;
}
Py_BEGIN_ALLOW_THREADS
err = zts_bind(fd, (struct zts_sockaddr *)&addrbuf, addrlen);
Py_END_ALLOW_THREADS
return err;
}
int zts_py_connect(int fd, int family, int type, PyObject *addr_obj)
{
struct zts_sockaddr_storage addrbuf;
int addrlen;
int err;
if (zts_py_tuple_to_sockaddr(family, addr_obj,
(struct zts_sockaddr *)&addrbuf, &addrlen) != ZTS_ERR_OK)
{
return ZTS_ERR_ARG;
}
Py_BEGIN_ALLOW_THREADS
err = zts_connect(fd, (struct zts_sockaddr *)&addrbuf, addrlen);
Py_END_ALLOW_THREADS
return err;
}
PyObject * zts_py_recv(int fd, int len, int flags)
{
PyObject *t, *buf;
int bytes_read;
buf = PyBytes_FromStringAndSize((char *) 0, len);
if (buf == NULL) {
return NULL;
}
bytes_read = zts_recv(fd, PyBytes_AS_STRING(buf), len, flags);
t = PyTuple_New(2);
PyTuple_SetItem(t, 0, PyLong_FromLong(bytes_read));
if (bytes_read < 0) {
Py_DECREF(buf);
Py_INCREF(Py_None);
PyTuple_SetItem(t, 1, Py_None);
Py_INCREF(t);
return t;
}
if (bytes_read != len) {
_PyBytes_Resize(&buf, bytes_read);
}
PyTuple_SetItem(t, 1, buf);
Py_INCREF(t);
return t;
}
int zts_py_send(int fd, PyObject *buf, int flags)
{
Py_buffer output;
int bytes_sent;
if (PyObject_GetBuffer(buf, &output, PyBUF_SIMPLE) != 0) {
return 0;
}
bytes_sent = zts_send(fd, output.buf, output.len, flags);
PyBuffer_Release(&output);
return bytes_sent;
}
int zts_py_close(int fd)
{
int err;
Py_BEGIN_ALLOW_THREADS
err = zts_close(fd);
Py_END_ALLOW_THREADS
return err;
}
#endif // ZTS_ENABLE_PYTHON

View File

@@ -0,0 +1,203 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
/**
* @file
*
* ZeroTier Socket API (Python)
*/
#include "ZeroTierSockets.h"
#include "lwip/inet.h"
#include "lwip/sockets.h"
#include <string.h>
#ifdef ZTS_ENABLE_PYTHON
int zts_py_setblocking(int fd, int block)
{
int new_flags, cur_flags, err = 0;
Py_BEGIN_ALLOW_THREADS cur_flags = zts_fcntl(fd, F_GETFL, 0);
if (cur_flags < 0) {
err = ZTS_ERR_SOCKET;
goto done;
}
if (! block) {
new_flags |= ZTS_O_NONBLOCK;
}
else {
new_flags &= ~ZTS_O_NONBLOCK;
}
if (new_flags != cur_flags) {
err = zts_fcntl(fd, F_SETFL, new_flags);
}
done:
Py_END_ALLOW_THREADS
return err;
}
int zts_py_getblocking(int fd)
{
int flags;
Py_BEGIN_ALLOW_THREADS flags = zts_fcntl(fd, F_GETFL, 0);
Py_END_ALLOW_THREADS
if (flags < 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) {
struct zts_sockaddr_in* addr;
char* host_str;
int result, port;
if (! PyTuple_Check(addr_obj)) {
return ZTS_ERR_ARG;
}
if (! PyArg_ParseTuple(addr_obj, "eti:zts_py_tuple_to_sockaddr", "idna", &host_str, &port)) {
return ZTS_ERR_ARG;
}
addr = (struct zts_sockaddr_in*)dst_addr;
zts_inet_pton(ZTS_AF_INET, host_str, &(addr->sin_addr.s_addr));
PyMem_Free(host_str);
if (port < 0 || port > 0xFFFF) {
return ZTS_ERR_ARG;
}
if (result < 0) {
return ZTS_ERR_ARG;
}
addr->sin_family = AF_INET;
addr->sin_port = lwip_htons((short)port);
*addrlen = sizeof *addr;
return ZTS_ERR_OK;
}
if (family == AF_INET6) {
// TODO
}
return ZTS_ERR_ARG;
}
PyObject* zts_py_accept(int fd)
{
struct zts_sockaddr_in addrbuf = { 0 };
socklen_t addrlen = sizeof(addrbuf);
int err = zts_accept(fd, (struct zts_sockaddr*)&addrbuf, &addrlen);
char ipstr[ZTS_INET_ADDRSTRLEN] = { 0 };
zts_inet_ntop(ZTS_AF_INET, &(addrbuf.sin_addr), ipstr, ZTS_INET_ADDRSTRLEN);
PyObject* t;
t = PyTuple_New(3);
PyTuple_SetItem(t, 0, PyLong_FromLong(err)); // New file descriptor
PyTuple_SetItem(t, 1, PyUnicode_FromString(ipstr));
PyTuple_SetItem(t, 2, PyLong_FromLong(lwip_ntohs(addrbuf.sin_port)));
Py_INCREF(t);
return t;
}
int zts_py_listen(int fd, int backlog)
{
if (backlog < 0) {
backlog = 128;
}
return zts_listen(fd, backlog);
}
int zts_py_bind(int fd, int family, int type, PyObject* addr_obj)
{
struct zts_sockaddr_storage addrbuf;
int addrlen;
int err;
if (zts_py_tuple_to_sockaddr(family, addr_obj, (struct zts_sockaddr*)&addrbuf, &addrlen) != ZTS_ERR_OK) {
return ZTS_ERR_ARG;
}
Py_BEGIN_ALLOW_THREADS err = zts_bind(fd, (struct zts_sockaddr*)&addrbuf, addrlen);
Py_END_ALLOW_THREADS return err;
}
int zts_py_connect(int fd, int family, int type, PyObject* addr_obj)
{
struct zts_sockaddr_storage addrbuf;
int addrlen;
int err;
if (zts_py_tuple_to_sockaddr(family, addr_obj, (struct zts_sockaddr*)&addrbuf, &addrlen) != ZTS_ERR_OK) {
return ZTS_ERR_ARG;
}
Py_BEGIN_ALLOW_THREADS err = zts_connect(fd, (struct zts_sockaddr*)&addrbuf, addrlen);
Py_END_ALLOW_THREADS return err;
}
PyObject* zts_py_recv(int fd, int len, int flags)
{
PyObject *t, *buf;
int bytes_read;
buf = PyBytes_FromStringAndSize((char*)0, len);
if (buf == NULL) {
return NULL;
}
bytes_read = zts_recv(fd, PyBytes_AS_STRING(buf), len, flags);
t = PyTuple_New(2);
PyTuple_SetItem(t, 0, PyLong_FromLong(bytes_read));
if (bytes_read < 0) {
Py_DECREF(buf);
Py_INCREF(Py_None);
PyTuple_SetItem(t, 1, Py_None);
Py_INCREF(t);
return t;
}
if (bytes_read != len) {
_PyBytes_Resize(&buf, bytes_read);
}
PyTuple_SetItem(t, 1, buf);
Py_INCREF(t);
return t;
}
int zts_py_send(int fd, PyObject* buf, int flags)
{
Py_buffer output;
int bytes_sent;
if (PyObject_GetBuffer(buf, &output, PyBUF_SIMPLE) != 0) {
return 0;
}
bytes_sent = zts_send(fd, output.buf, output.len, flags);
PyBuffer_Release(&output);
return bytes_sent;
}
int zts_py_close(int fd)
{
int err;
Py_BEGIN_ALLOW_THREADS err = zts_close(fd);
Py_END_ALLOW_THREADS return err;
}
#endif // ZTS_ENABLE_PYTHON

View File

@@ -0,0 +1,79 @@
import libzt
_user_specified_event_handler = None
class _EventCallbackClass(libzt.PythonDirectorCallbackClass):
"""ZeroTier event callback class"""
pass
class MyEventCallbackClass(_EventCallbackClass):
def on_zerotier_event(self, msg):
id = 0
if msg.event_code == libzt.ZTS_EVENT_NODE_ONLINE:
id = msg.node.node_id
if msg.event_code == libzt.ZTS_EVENT_NODE_OFFLINE:
id = msg.node.node_id
if msg.event_code == libzt.ZTS_EVENT_NETWORK_READY_IP4:
id = msg.network.net_id
if msg.event_code == libzt.ZTS_EVENT_NETWORK_READY_IP6:
id = msg.network.net_id
if msg.event_code == libzt.ZTS_EVENT_PEER_DIRECT:
id = msg.peer.peer_id
if msg.event_code == libzt.ZTS_EVENT_PEER_RELAY:
id = msg.peer.peer_id
# Now that we've adjusted internal state, notify user
global _user_specified_event_handler
_user_specified_event_handler(msg.event_code, id)
class ZeroTierNode:
native_event_handler = None
def __init__(self):
self.native_event_handler = MyEventCallbackClass()
libzt.zts_init_set_event_handler(self.native_event_handler)
"""ZeroTier Node"""
def init_from_storage(self, storage_path):
"""Initialize the node from storage (or tell it to write to that location)"""
return libzt.zts_init_from_storage(storage_path)
"""Set the node's event handler"""
def init_set_event_handler(self, event_handler):
global _user_specified_event_handler
_user_specified_event_handler = event_handler
def init_set_port(self, port):
"""Set the node's primary port"""
return libzt.zts_init_set_port(port)
def node_start(self):
"""Start the ZeroTier service"""
return libzt.zts_node_start()
def node_stop(self):
"""Stop the ZeroTier service"""
return libzt.zts_node_stop()
def node_free(self):
"""Permanently shut down the network stack"""
return libzt.zts_node_free()
def net_join(self, net_id):
"""Join a ZeroTier network"""
return libzt.zts_net_join(net_id)
def net_leave(self, net_id):
"""Leave a ZeroTier network"""
return libzt.zts_net_leave(net_id)
def node_is_online(self):
return libzt.zts_node_is_online()
def net_transport_is_ready(self, net_id):
return libzt.zts_net_transport_is_ready(net_id)

44
src/bindings/python/sockets.py Normal file → Executable file
View File

@@ -2,9 +2,6 @@
import libzt
class EventCallbackClass(libzt.PythonDirectorCallbackClass):
"""ZeroTier event callback class"""
pass
def handle_error(err):
"""Convert libzt error code to exception"""
@@ -20,6 +17,7 @@ def handle_error(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
@@ -28,41 +26,10 @@ def errno():
"""Return errno value of low-level socket layer"""
return libzt.cvar.zts_errno
def start(path, callback, port):
"""Start the ZeroTier service"""
libzt.zts_start(path, callback, port)
def stop():
"""Stop the ZeroTier service"""
libzt.zts_stop()
def restart():
"""[debug] Restarts the ZeroTier service and network stack"""
libzt.zts_restart()
def free():
"""Permenantly shuts down the network stack"""
libzt.zts_free()
def join(network_id):
"""Join a ZeroTier network"""
libzt.zts_join(network_id)
def leave(network_id):
"""Leave a ZeroTier network"""
libzt.zts_leave(network_id)
def zts_orbit(moon_world_id, moon_seed):
"""Orbit a moon"""
return libzt.zts_orbit(moon_world_id, moon_seed)
def zts_deorbit(moon_world_id):
"""De-orbit a moon"""
return libzt.zts_deorbit(moon_world_id)
class socket:
"""Pythonic class that wraps low-level sockets"""
_fd = -1 # native layer file descriptor
_family = -1
_type = -1
@@ -131,7 +98,9 @@ class socket:
"""libzt does not support this (yet)"""
raise NotImplementedError("libzt does not support this (yet?)")
def getaddrinfo(self, host, port, sock_family=0, sock_type=0, sock_proto=0, flags=0):
def getaddrinfo(
self, host, port, sock_family=0, sock_type=0, sock_proto=0, flags=0
):
"""libzt does not support this (yet)"""
raise NotImplementedError("libzt does not support this (yet?)")
@@ -285,7 +254,8 @@ class socket:
def detach(self):
"""libzt does not support this"""
raise NotImplementedError(
"detach(): Not supported. OS File descriptors aren't used in libzt.")
"detach(): Not supported. OS File descriptors aren't used in libzt."
)
def dup(self):
"""libzt does not support this"""

133
test/selftest.py Normal file
View File

@@ -0,0 +1,133 @@
'''Example low-level socket usage'''
import time
import sys
import libzt
def print_usage():
'''print help'''
print(
"\nUsage: <server|client> <storage_path> <net_id> <remote_ip> <remote_port>\n"
)
print("Ex: python3 example.py server . 0123456789abcdef 8080")
print("Ex: python3 example.py client . 0123456789abcdef 192.168.22.1 8080\n")
if len(sys.argv) < 5:
print("Too few arguments")
if len(sys.argv) > 6:
print("Too many arguments")
sys.exit(0)
#
# (Optional) Event handler
#
def on_zerotier_event(event_code, id):
if event_code == libzt.ZTS_EVENT_NODE_ONLINE:
print("ZTS_EVENT_NODE_ONLINE (" + str(event_code) + ") : " + hex(id))
if event_code == libzt.ZTS_EVENT_NODE_OFFLINE:
print("ZTS_EVENT_NODE_OFFLINE (" + str(event_code) + ") : " + hex(id))
if event_code == libzt.ZTS_EVENT_NETWORK_READY_IP4:
print("ZTS_EVENT_NETWORK_READY_IP4 (" + str(event_code) + ") : " + hex(id))
if event_code == libzt.ZTS_EVENT_NETWORK_READY_IP6:
print("ZTS_EVENT_NETWORK_READY_IP6 (" + str(event_code) + ") : " + hex(id))
if event_code == libzt.ZTS_EVENT_PEER_DIRECT:
print("ZTS_EVENT_PEER_DIRECT (" + str(event_code) + ") : " + hex(id))
if event_code == libzt.ZTS_EVENT_PEER_RELAY:
print("ZTS_EVENT_PEER_RELAY (" + str(event_code) + ") : " + hex(id))
#
# Main
#
def main():
mode = None # client|server
storage_path = "." # Where identity files are stored
net_id = 0 # Network to join
remote_ip = None # ZeroTier IP of remote node
remote_port = 8080 # ZeroTier port your app logic may use
if len(sys.argv) < 5 or len(sys.argv) > 6:
print_usage()
if sys.argv[1] == "server" and len(sys.argv) == 5:
mode = sys.argv[1]
storage_path = sys.argv[2]
net_id = int(sys.argv[3], 16)
remote_port = int(sys.argv[4])
if sys.argv[1] == "client" and len(sys.argv) == 6:
mode = sys.argv[1]
storage_path = sys.argv[2]
net_id = int(sys.argv[3], 16)
remote_ip = sys.argv[4]
remote_port = int(sys.argv[5])
if mode is None:
print_usage()
print("mode = ", mode)
print("storage_path = ", storage_path)
print("net_id = ", net_id)
print("remote_ip = ", remote_ip)
print("remote_port = ", remote_port)
#
# Node initialization and start
#
print("Starting ZeroTier...")
n = libzt.ZeroTierNode()
n.init_set_event_handler(on_zerotier_event) # Optional
n.init_from_storage(storage_path) # Optional
n.init_set_port(9994) # Optional
n.node_start()
print("Waiting for node to come online...")
while not n.node_is_online():
time.sleep(1)
print("Joining network:", hex(net_id))
n.net_join(net_id)
while not n.net_transport_is_ready(net_id):
time.sleep(1)
print("Joined network")
#
# Example server
#
if mode == "server":
print("Starting server...")
serv = libzt.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0)
try:
# serv.setblocking(True)
serv.bind(("0.0.0.0", remote_port))
serv.listen(5)
conn, addr = serv.accept()
print("Accepted connection from: ", addr)
data = conn.recv(4096)
if data:
print("recv: ", data)
print("send: ", data)
sent_bytes = conn.send(data) # echo back to the server
print("sent: " + str(sent_bytes) + " byte(s)")
conn.close()
print("client disconnected")
except Exception as ex:
print(ex)
print("errno=", libzt.errno()) # See include/ZeroTierSockets.h for codes
#
# Example client
#
if mode == "client":
print("Starting client...")
client = libzt.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0)
try:
print("connecting...")
client.connect((remote_ip, remote_port))
data = "Hello, world!"
print("send: ", data)
sent_bytes = client.send(data.encode('utf-8'))
print("sent: " + str(sent_bytes) + " byte(s)")
data = client.recv(1024)
print("recv: ", repr(data))
except Exception as ex:
print(ex)
print("errno=", libzt.errno())
if __name__ == "__main__":
main()