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 - name: Checkout repo
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Install black
run: pip3 install black
- name: Format code - name: Format code
run: ./build.sh format-code run: ./build.sh format-code "all"
- name: Commit changes - name: Commit changes
uses: joseph-henry/add-and-commit@v7 uses: joseph-henry/add-and-commit@v7

126
build.sh
View File

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

View File

@@ -1,106 +1,92 @@
'''Example low-level socket usage''' """Example low-level socket usage"""
import time import time
import sys import sys
import libzt import libzt
def print_usage(): def print_usage():
'''print help''' """print help"""
print( 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 example.py server . 0123456789abcdef 8080")
print("Ex: python3 demo.py client . 0123456789abcdef 9994 192.168.22.1 8080\n") print("Ex: python3 example.py client . 0123456789abcdef 192.168.22.1 8080\n")
if len(sys.argv) < 6: if len(sys.argv) < 5:
print("Too few arguments") print("Too few arguments")
if len(sys.argv) > 7: if len(sys.argv) > 6:
print("Too many arguments") print("Too many arguments")
sys.exit(0) 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 # Main
# #
def main(): def main():
global is_online mode = None # client|server
global is_joined 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
key_file_path = "." # Where identity files are stored if len(sys.argv) < 5 or len(sys.argv) > 6:
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
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:
print_usage() 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] mode = sys.argv[1]
key_file_path = sys.argv[2] storage_path = sys.argv[2]
network_id = int(sys.argv[3], 16) net_id = int(sys.argv[3], 16)
zt_service_port = int(sys.argv[4]) 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]) 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: if mode is None:
print_usage() print_usage()
print("mode = ", mode) print("mode = ", mode)
print("path = ", key_file_path) print("storage_path = ", storage_path)
print("network_id = ", network_id) print("net_id = ", net_id)
print("zt_service_port = ", zt_service_port) print("remote_ip = ", remote_ip)
print("remote_ip = ", remote_ip) print("remote_port = ", remote_port)
print("remote_port = ", remote_port)
# #
# Example start and join logic # Node initialization and start
# #
print("Starting ZeroTier...") 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...") 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) 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") print("Joined network")
# #
@@ -140,7 +126,7 @@ def main():
try: try:
print("connecting...") print("connecting...")
client.connect((remote_ip, remote_port)) client.connect((remote_ip, remote_port))
data = "Hello, world!" data = "Hello, roots!"
print("send: ", data) print("send: ", data)
sent_bytes = client.send(data) sent_bytes = client.send(data)
print("sent: " + str(sent_bytes) + " byte(s)") print("sent: " + str(sent_bytes) + " byte(s)")
@@ -150,5 +136,6 @@ def main():
print(ex) print(ex)
print("errno=", libzt.errno()) print("errno=", libzt.errno())
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -1058,6 +1058,10 @@ int zts_py_getblocking(int fd);
// Central API // // Central API //
//----------------------------------------------------------------------------// //----------------------------------------------------------------------------//
#ifdef ZTS_ENABLE_PYTHON
#define ZTS_DISABLE_CENTRAL_API 1
#endif
#ifndef ZTS_DISABLE_CENTRAL_API #ifndef ZTS_DISABLE_CENTRAL_API
#define ZTS_CENTRAL_DEFAULT_URL "https://my.zerotier.com" #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 # Symbolic link to source tree so that sdist structure makes sense
ln -s ../../ native ln -s ../../ native
# Re-build wrapper to export C symbols # 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 # Copy language bindings into module directory
cp -f native/src/bindings/python/*.py libzt/ cp -f native/src/bindings/python/*.py libzt/
cp -f native/LICENSE.txt LICENSE cp -f native/LICENSE.txt LICENSE
@@ -30,6 +30,7 @@ clean()
find . -name '__pycache__' -type d -delete find . -name '__pycache__' -type d -delete
rm -rf libzt/sockets.py rm -rf libzt/sockets.py
rm -rf libzt/libzt.py rm -rf libzt/libzt.py
rm -rf libzt/node.py
rm -rf src ext build dist native rm -rf src ext build dist native
rm -rf libzt.egg-info rm -rf libzt.egg-info
rm -rf LICENSE rm -rf LICENSE

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,27 @@ class BinaryDistribution(Distribution):
def is_pure(self): def is_pure(self):
return False 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 = [] cpp_glob = []
c_glob = [] c_glob = []
@@ -30,7 +51,7 @@ if os.name == 'nt':
# Everything else # Everything else
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/src/*.cpp')))
cpp_glob.extend(list(glob.glob('native/ext/ZeroTierOne/node/*.cpp'))) cpp_glob.extend(list(glob.glob('native/ext/ZeroTierOne/node/*.cpp')))
cpp_glob.extend(list(glob.glob('native/ext/ZeroTierOne/osdep/OSUtils.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/src/include',
'native/ext/lwip-contrib/ports/unix/port/include', 'native/ext/lwip-contrib/ports/unix/port/include',
'native/ext/ZeroTierOne/include', 'native/ext/ZeroTierOne/include',
'native/ext/ZeroTierOne',
'native/ext/ZeroTierOne/node', 'native/ext/ZeroTierOne/node',
'native/ext/ZeroTierOne/service', 'native/ext/ZeroTierOne/service',
'native/ext/ZeroTierOne/osdep', 'native/ext/ZeroTierOne/osdep',
'native/ext/ZeroTierOne/controller'] 'native/ext/ZeroTierOne/controller']
libzt_module = Extension('libzt._libzt', 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) sources=cpp_glob, include_dirs=my_include_dirs)
# Separate C library, this is needed since C++ compiler flags are applied # 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 #ifndef ZTS_DISABLE_CENTRAL_API
#include "Debug.hpp" #include "Debug.hpp"
#include "Mutex.hpp" #include "Mutex.hpp"
#include "OSUtils.hpp" #include "OSUtils.hpp"
#include "ZeroTierSockets.h"
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>

View File

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

View File

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

View File

@@ -26,9 +26,9 @@
#ifdef ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS #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 #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)

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

@@ -2,9 +2,6 @@
import libzt import libzt
class EventCallbackClass(libzt.PythonDirectorCallbackClass):
"""ZeroTier event callback class"""
pass
def handle_error(err): def handle_error(err):
"""Convert libzt error code to exception""" """Convert libzt error code to exception"""
@@ -20,6 +17,7 @@ def handle_error(err):
if err == libzt.ZTS_ERR_GENERAL: if err == libzt.ZTS_ERR_GENERAL:
raise Exception("ZTS_ERR_GENERAL (" + str(err) + ")") raise Exception("ZTS_ERR_GENERAL (" + str(err) + ")")
# This implementation of errno is NOT thread safe # This implementation of errno is NOT thread safe
# That is, this value is shared among all lower-level socket calls # 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 # 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 errno value of low-level socket layer"""
return libzt.cvar.zts_errno 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: class socket:
"""Pythonic class that wraps low-level sockets""" """Pythonic class that wraps low-level sockets"""
_fd = -1 # native layer file descriptor _fd = -1 # native layer file descriptor
_family = -1 _family = -1
_type = -1 _type = -1
@@ -131,7 +98,9 @@ class socket:
"""libzt does not support this (yet)""" """libzt does not support this (yet)"""
raise NotImplementedError("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)""" """libzt does not support this (yet)"""
raise NotImplementedError("libzt does not support this (yet?)") raise NotImplementedError("libzt does not support this (yet?)")
@@ -285,7 +254,8 @@ class socket:
def detach(self): def detach(self):
"""libzt does not support this""" """libzt does not support this"""
raise NotImplementedError( 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): def dup(self):
"""libzt does not support this""" """libzt does not support this"""
@@ -336,7 +306,7 @@ class socket:
if backlog is not None and backlog < 0: if backlog is not None and backlog < 0:
backlog = 0 backlog = 0
if backlog is None: if backlog is None:
backlog = -1 # Lower-level code picks default backlog = -1 # Lower-level code picks default
err = libzt.zts_py_listen(self._fd, backlog) err = libzt.zts_py_listen(self._fd, backlog)
if err < 0: if err < 0:

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()