Add working Python wrapper and examples (WIP)
This commit is contained in:
148
src/bindings/python/PythonSockets.cpp
Normal file
148
src/bindings/python/PythonSockets.cpp
Normal file
@@ -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
|
||||
4
src/bindings/python/README.md
Normal file
4
src/bindings/python/README.md
Normal file
@@ -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/)
|
||||
103
src/bindings/python/prototype.py
Normal file
103
src/bindings/python/prototype.py
Normal file
@@ -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)
|
||||
39
src/bindings/python/zt.i
Normal file
39
src/bindings/python/zt.i
Normal file
@@ -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"
|
||||
Reference in New Issue
Block a user