Building the Zerotier SDK (libzt) Python bindings
The Zerotier SDK, a.k.a. libzt
connects to a Zerotier network in userspace,
without creating a virtual network interface.
It provides bindings for quite a few languages, and I’m interested in the Python binding.
As of today, there are only pre-built wheels for Python 3.5 - 3.9 on PyPI, and they are pretty old.
So here’s how I compiled libzt
Python bindings for Python 3.11, both on Linux amd64 and termux
.
These notes should be helpful for building on other Python versions as well.
Prerequisites
Clone libzt
’s git repository.
Install cmake
and swig
via distro’s package manager.
Make a new Python 3 venv, and pip install poetry
.
This is simple on regular Linux,
but for termux
,
the installation will fail because it can’t build cryptography
,
a dependency of a dependency of a dependency,
from source.
Termux Wiki explains this.
The solution:
# install precompiled cryptography from package manager
pkg install python-cryptography
# create venv that uses system packages
python -m venv --system-site-packages ./venv
# and then install poetry in this venv
. ./venv/bin/activate
pip install poetry
Fixing the build process
libzt
adapted poetry
for its Python bindings build process a while ago,
but hasn’t changed its build scripts to match.
Therefore, simply running build.sh host-python release
does not work.
However, when using a modern version of poetry
,
cd
-ing into pkg\pypi
and running poetry build
does not work either,
producing only empty packages that does not contain any code.
This is because poetry
still does not have a stable, documented way of building native code extensions,
and the current method no longer works due to a change in defaults.
Solution:
diff --git a/pkg/pypi/pyproject.toml b/pkg/pypi/pyproject.toml
index 84e63a7..a79f122 100644
--- a/pkg/pypi/pyproject.toml
+++ b/pkg/pypi/pyproject.toml
@@ -44,7 +44,10 @@ classifiers = [
packages = [
{ include = "libzt" },
]
-build = "build.py"
+
+[tool.poetry.build]
+script = "build.py"
+generate-setup-file = true
[tool.poetry.dependencies]
python = "^3.6"
Then, remember to delete the existing setup.py
, so that poetry
can regenerate it.
Now running poetry build
will actually attempt to build something!
Just try it, and fix any problems that come up.
Code fixes
If there are errors about prometheus
includes not being found:
diff --git a/pkg/pypi/build.py b/pkg/pypi/build.py
index 557a648..b58d6cb 100644
--- a/pkg/pypi/build.py
+++ b/pkg/pypi/build.py
@@ -27,6 +27,8 @@ INCLUDE_DIRS = [
os.path.join(ROOT_DIR, "ext/ZeroTierOne/include"),
os.path.join(ROOT_DIR, "ext/ZeroTierOne"),
os.path.join(ROOT_DIR, "ext/ZeroTierOne/ext"),
+ os.path.join(ROOT_DIR, "ext/ZeroTierOne/ext/prometheus-cpp-lite-1.0/simpleapi/include"),
+ os.path.join(ROOT_DIR, "ext/ZeroTierOne/ext/prometheus-cpp-lite-1.0/core/include"),
os.path.join(ROOT_DIR, "ext/ZeroTierOne/node"),
os.path.join(ROOT_DIR, "ext/ZeroTierOne/service"),
os.path.join(ROOT_DIR, "ext/ZeroTierOne/osdep"),
If the compiler complains about _PyTime_AsTimeval_noraise
nont existing:
diff --git a/src/bindings/python/PythonSockets.cxx b/src/bindings/python/PythonSockets.cxx
index f078284..54b2cdb 100644
--- a/src/bindings/python/PythonSockets.cxx
+++ b/src/bindings/python/PythonSockets.cxx
@@ -479,7 +479,7 @@ PyObject* zts_py_select(PyObject* module, PyObject* rlist, PyObject* wlist, PyOb
n = 0;
break;
}
- _PyTime_AsTimeval_noraise(timeout, &tv, _PyTime_ROUND_CEILING);
+ _PyTime_AsTimeval_clamp(timeout, &tv, _PyTime_ROUND_CEILING);
/* retry select() with the recomputed timeout */
}
} while (1);
(I have to assume this is a private method because of the leading underscore. It was also changed quite a while ago.)
Done!
There should be a wheel in pkg/pypi/dist
.
Install it.
cd
somewhere else to make sure there is not a directory called libzt
in your current working directory,
so that Python does not get confused when you import libzt
.
It should work now.