8 changed files with 491 additions and 30 deletions
@ -0,0 +1,15 @@
|
||||
LIB_OUTPUT_DIR = $(shell cd ../../ && ./build.sh gethosttype)
|
||||
|
||||
copy_wrapper_sources: |
||||
cp -f ../../src/bindings/python/*.py .
|
||||
|
||||
debug: copy_wrapper_sources |
||||
cd ../../ && ./build.sh host-python "debug"
|
||||
cp -f ../../dist/$(LIB_OUTPUT_DIR)-python-debug/lib/*.so .
|
||||
|
||||
release: copy_wrapper_sources |
||||
cd ../../ && ./build.sh host-python "release"
|
||||
cp -f ../../dist/$(LIB_OUTPUT_DIR)-python-release/lib/*.so .
|
||||
|
||||
clean: |
||||
rm -rf *.so libzt.py prototype.py
|
||||
@ -0,0 +1,23 @@
|
||||
# Python example |
||||
|
||||
This example demonstrates how to use the ZeroTier socket interface provided by libzt in a Python application. The API is designed to be a drop-in replacement for the Python [Low-level networking interface](https://docs.python.org/3/library/socket.html). |
||||
|
||||
Note: Only `AF_INET` and `AF_INET6` address families are supported. |
||||
|
||||
### Install |
||||
|
||||
``` |
||||
pip install libzt |
||||
``` |
||||
|
||||
### Run |
||||
``` |
||||
python3 example.py server id-path/bob 0123456789abcdef 9997 8080 |
||||
python3 example.py client id-path/alice 0123456789abcdef 9996 11.22.33.44 8080 |
||||
``` |
||||
|
||||
*Where `9996` and `9997` are arbitrary ports that you allow ZeroTier to use for encrypted UDP traffic, port `8080` is an arbitrary port used by the client/server socket code, and `11.22.33.44` should be whatever IP address the network assigns your node.* |
||||
|
||||
### Implementation Details |
||||
|
||||
- See [src/bindings/python](../../src/bindings/python) |
||||
@ -0,0 +1,159 @@
|
||||
import time, sys |
||||
|
||||
import libzt |
||||
from prototype import * |
||||
|
||||
# Where identity files are stored |
||||
keyPath = "." |
||||
|
||||
# Network to join |
||||
networkId = 0 |
||||
|
||||
# Port used by ZeroTier to send encpryted UDP traffic |
||||
# NOTE: Should be different from other instances of ZeroTier |
||||
# running on the same machine |
||||
ztServicePort = 9997 |
||||
|
||||
remoteIP = None |
||||
|
||||
# A port your app logic may use |
||||
serverPort = 8080 |
||||
|
||||
# Flags to keep state |
||||
is_joined = False |
||||
is_online = False |
||||
mode = None |
||||
|
||||
def print_usage(): |
||||
print("\nUsage: <server|client> <id_path> <nwid> <ztServicePort> <remoteIP> <serverPort>\n") |
||||
print(" Ex: python3 example.py server . 0123456789abcdef 9994 8080") |
||||
print(" Ex: python3 example.py client . 0123456789abcdef 9994 192.168.22.1 8080\n") |
||||
if (len(sys.argv) < 6): |
||||
print('Too few arguments') |
||||
if (len(sys.argv) > 7): |
||||
print('Too many arguments') |
||||
exit(0) |
||||
# |
||||
if (len(sys.argv) < 6 or len(sys.argv) > 7): |
||||
print_usage() |
||||
|
||||
if (sys.argv[1] == 'server' and len(sys.argv) == 6): |
||||
mode = sys.argv[1] |
||||
keyPath = sys.argv[2] |
||||
networkId = int(sys.argv[3],16) |
||||
ztServicePort = int(sys.argv[4]) |
||||
serverPort = int(sys.argv[5]) |
||||
|
||||
if (sys.argv[1] == 'client' and len(sys.argv) == 7): |
||||
mode = sys.argv[1] |
||||
keyPath = sys.argv[2] |
||||
networkId = int(sys.argv[3],16) |
||||
ztServicePort = int(sys.argv[4]) |
||||
remoteIP = sys.argv[5] |
||||
serverPort = int(sys.argv[6]) |
||||
|
||||
if (mode is None): |
||||
print_usage() |
||||
|
||||
print('mode = ', mode) |
||||
print('path = ', keyPath) |
||||
print('networkId = ', networkId) |
||||
print('ztServicePort = ', ztServicePort) |
||||
print('remoteIP = ', remoteIP) |
||||
print('serverPort = ', serverPort) |
||||
|
||||
|
||||
|
||||
# |
||||
# Event handler |
||||
# |
||||
class MyEventCallbackClass(libzt.PythonDirectorCallbackClass): |
||||
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") |
||||
|
||||
|
||||
|
||||
# |
||||
# Example start and join logic |
||||
# |
||||
print("Starting ZeroTier..."); |
||||
eventCallback = MyEventCallbackClass() |
||||
libzt.zts_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) |
||||
while (not is_joined): |
||||
time.sleep(1) # You can ping this app at this point |
||||
print('Joined network') |
||||
|
||||
|
||||
|
||||
# |
||||
# Example server |
||||
# |
||||
if (mode == 'server'): |
||||
print("Starting server...") |
||||
try: |
||||
serv = zerotier.socket(libzt.ZTS_AF_INET6, libzt.ZTS_SOCK_STREAM, 0) |
||||
serv.bind(('::', serverPort)) |
||||
serv.listen(5) |
||||
while True: |
||||
conn, addr = serv.accept() |
||||
print('Accepted connection from: ', addr) |
||||
while True: |
||||
print('recv()...') |
||||
data = conn.recv(4096) |
||||
if data: |
||||
print('data = ', data) |
||||
#print(type(b'what')) |
||||
#exit(0) |
||||
if not data: break |
||||
print('send()...') |
||||
#bytes(data, 'ascii') + b'\x00' |
||||
n_bytes = conn.send(data) # echo back to the server |
||||
print('sent ' + str(n_bytes) + ' byte(s)') |
||||
conn.close() |
||||
print('client disconnected') |
||||
except Exception as e: |
||||
print(e) |
||||
|
||||
|
||||
# |
||||
# Example client |
||||
# |
||||
if (mode == 'client'): |
||||
print("Starting client...") |
||||
try: |
||||
client = zerotier.socket(libzt.ZTS_AF_INET6, libzt.ZTS_SOCK_STREAM, 0) |
||||
print("connecting...") |
||||
client.connect((remoteIP, serverPort)) |
||||
print("send...") |
||||
data = 'Hello, world!' |
||||
client.send(data) |
||||
print("rx...") |
||||
data = client.recv(1024) |
||||
|
||||
print('Received', repr(data)) |
||||
except Exception as e: |
||||
print(e) |
||||
|
||||
@ -1,30 +0,0 @@
|
||||
/* libzt.i */ |
||||
|
||||
%begin |
||||
%{ |
||||
#define SWIG_PYTHON_CAST_MODE |
||||
%} |
||||
|
||||
%include <stdint.i> |
||||
|
||||
#define PYTHON_BUILD 1 |
||||
|
||||
%module libzt |
||||
%{ |
||||
#include "../include/ZeroTier.h" |
||||
#include "../include/ZeroTierConstants.h" |
||||
%} |
||||
|
||||
%define %cs_callback(TYPE, CSTYPE) |
||||
%typemap(ctype) TYPE, TYPE& "void *" |
||||
%typemap(in) TYPE %{ $1 = ($1_type)$input; %} |
||||
%typemap(in) TYPE& %{ $1 = ($1_type)&$input; %} |
||||
%typemap(imtype, out="IntPtr") TYPE, TYPE& "CSTYPE" |
||||
%typemap(cstype, out="IntPtr") TYPE, TYPE& "CSTYPE" |
||||
%typemap(csin) TYPE, TYPE& "$csinput" |
||||
%enddef |
||||
|
||||
%cs_callback(userCallbackFunc, CSharpCallback) |
||||
|
||||
%include "../include/ZeroTier.h" |
||||
%include "../include/ZeroTierConstants.h" |
||||
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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 "lwip/sockets.h" |
||||
#include "ZeroTierSockets.h" |
||||
|
||||
#ifdef ZTS_ENABLE_PYTHON |
||||
|
||||
static int 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:tuple_to_sockaddr", "idna", &host_str, &port)) { |
||||
return ZTS_ERR_ARG; |
||||
} |
||||
addr = (struct zts_sockaddr_in*)dst_addr; |
||||
addr->sin_addr.s_addr = zts_inet_addr(host_str); |
||||
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 = 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(zts_ntohs(addrbuf.sin_port))); |
||||
Py_INCREF(t); |
||||
return t; |
||||
} |
||||
|
||||
int zts_py_listen(int fd, int backlog) |
||||
{ |
||||
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 (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 |
||||
Py_INCREF(Py_None); |
||||
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 (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 |
||||
Py_INCREF(Py_None); |
||||
return err; |
||||
} |
||||
|
||||
PyObject * zts_py_recv(int fd, int len, int flags) |
||||
{ |
||||
PyObject *t; |
||||
char buf[4096]; |
||||
int err = zts_recv(fd, buf, len, flags); |
||||
if (err < 0) { |
||||
return NULL; |
||||
} |
||||
t = PyTuple_New(2); |
||||
PyTuple_SetItem(t, 0, PyLong_FromLong(err)); |
||||
PyTuple_SetItem(t, 1, PyUnicode_FromString(buf)); |
||||
Py_INCREF(t); |
||||
return t; |
||||
} |
||||
|
||||
int zts_py_send(int fd, PyObject *buf, int len, int flags) |
||||
{ |
||||
int err = ZTS_ERR_OK; |
||||
PyObject *encodedStr = PyUnicode_AsEncodedString(buf, "UTF-8", "strict"); |
||||
if (encodedStr) { |
||||
char *bytes = PyBytes_AsString(encodedStr); |
||||
err = zts_send(fd, bytes, len, flags); |
||||
Py_DECREF(encodedStr); |
||||
} |
||||
return err; |
||||
} |
||||
|
||||
int zts_py_close(int fd) |
||||
{ |
||||
return zts_close(fd); |
||||
} |
||||
|
||||
#endif // ZTS_ENABLE_PYTHON
|
||||
@ -0,0 +1,4 @@
|
||||
# Python Language Bindings |
||||
|
||||
- Install (via [PyPI package](https://pypi.org/project/libzt/)): `pip install libzt` |
||||
- Example usage: [examples/python](./../../../examples/python/) |
||||
@ -0,0 +1,103 @@
|
||||
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) |
||||
@ -0,0 +1,39 @@
|
||||
/* libzt.i */ |
||||
|
||||
%begin |
||||
%{ |
||||
#define SWIG_PYTHON_CAST_MODE |
||||
%} |
||||
|
||||
%include <stdint.i> |
||||
|
||||
#define ZTS_ENABLE_PYTHON 1 |
||||
|
||||
%module(directors="1") libzt |
||||
%module libzt |
||||
%{ |
||||
#include "ZeroTierSockets.h" |
||||
%} |
||||
|
||||
%feature("director") PythonDirectorCallbackClass; |
||||
|
||||
%ignore zts_in6_addr; |
||||
%ignore zts_sockaddr; |
||||
%ignore zts_in_addr; |
||||
%ignore zts_sockaddr_in; |
||||
%ignore zts_sockaddr_storage; |
||||
%ignore zts_sockaddr_in6; |
||||
|
||||
%ignore zts_linger; |
||||
%ignore zts_accept4; |
||||
%ignore zts_ip_mreq; |
||||
%ignore zts_in_pktinfo; |
||||
%ignore zts_ipv6_mreq; |
||||
|
||||
%ignore zts_fd_set; |
||||
%ignore zts_pollfd; |
||||
%ignore zts_nfds_t; |
||||
%ignore zts_msghdr; |
||||
%ignore zts_inet_addr; |
||||
|
||||
%include "ZeroTierSockets.h" |
||||
Loading…
Reference in new issue