Add select, getsockopt, setsockopt, ioctl and fcntl to Python wrapper

This commit is contained in:
Joseph Henry
2021-05-28 16:15:38 -07:00
parent 96684860fe
commit cd3265f8ae
6 changed files with 535 additions and 87 deletions

View File

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

View File

@@ -15,56 +15,35 @@
* @file * @file
* *
* ZeroTier Socket API (Python) * ZeroTier Socket API (Python)
*
* This code derives from the Python standard library:
*
* Lib/socket.py
* Modules/socketmodule.c
* Modules/fcntlmodule.c
* Modules/clinic/fcntlmodule.c.h
* Modules/clinic/selectmodule.c.h
*
* Copyright and license text can be found in pypi packaging directory.
*
*/ */
#include "ZeroTierSockets.h" #include "ZeroTierSockets.h"
#include "lwip/inet.h"
#include "lwip/sockets.h"
#include <string.h>
#ifdef ZTS_ENABLE_PYTHON #ifdef ZTS_ENABLE_PYTHON
int zts_py_setblocking(int fd, int block) #include "Python.h"
#include "PythonSockets.h"
#include "lwip/inet.h"
#include "lwip/sockets.h"
#include "structmember.h" // PyMemberDef
#include <string.h>
#include <sys/time.h>
PyObject* set_error(void)
{ {
int new_flags, cur_flags, err = 0; return NULL; // PyErr_SetFromErrno(zts_errno);
Py_BEGIN_ALLOW_THREADS cur_flags = zts_bsd_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_bsd_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_bsd_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) static int zts_py_tuple_to_sockaddr(int family, PyObject* addr_obj, struct zts_sockaddr* dst_addr, int* addrlen)
@@ -115,14 +94,6 @@ PyObject* zts_py_accept(int fd)
return t; return t;
} }
int zts_py_listen(int fd, int backlog)
{
if (backlog < 0) {
backlog = 128;
}
return zts_bsd_listen(fd, backlog);
}
int zts_py_bind(int fd, int family, int type, PyObject* addr_obj) int zts_py_bind(int fd, int family, int type, PyObject* addr_obj)
{ {
struct zts_sockaddr_storage addrbuf; struct zts_sockaddr_storage addrbuf;
@@ -211,4 +182,469 @@ PyObject* zts_py_addr_get_str(uint64_t net_id, int family)
return t; return t;
} }
/* list of Python objects and their file descriptor */
typedef struct {
PyObject* obj; /* owned reference */
int fd;
int sentinel; /* -1 == sentinel */
} pylist;
void reap_obj(pylist fd2obj[ZTS_FD_SETSIZE + 1])
{
unsigned int i;
for (i = 0; i < (unsigned int)ZTS_FD_SETSIZE + 1 && fd2obj[i].sentinel >= 0; i++) {
Py_CLEAR(fd2obj[i].obj);
}
fd2obj[0].sentinel = -1;
}
/* returns NULL and sets the Python exception if an error occurred */
PyObject* set2list(zts_fd_set* set, pylist fd2obj[ZTS_FD_SETSIZE + 1])
{
int i, j, count = 0;
PyObject *list, *o;
int fd;
for (j = 0; fd2obj[j].sentinel >= 0; j++) {
if (ZTS_FD_ISSET(fd2obj[j].fd, set)) {
count++;
}
}
list = PyList_New(count);
if (! list) {
return NULL;
}
i = 0;
for (j = 0; fd2obj[j].sentinel >= 0; j++) {
fd = fd2obj[j].fd;
if (ZTS_FD_ISSET(fd, set)) {
o = fd2obj[j].obj;
fd2obj[j].obj = NULL;
/* transfer ownership */
if (PyList_SetItem(list, i, o) < 0) {
goto finally;
}
i++;
}
}
return list;
finally:
Py_DECREF(list);
return NULL;
}
/* returns -1 and sets the Python exception if an error occurred, otherwise
returns a number >= 0
*/
int seq2set(PyObject* seq, zts_fd_set* set, pylist fd2obj[FD_SETSIZE + 1])
{
int max = -1;
unsigned int index = 0;
Py_ssize_t i;
PyObject* fast_seq = NULL;
PyObject* o = NULL;
fd2obj[0].obj = (PyObject*)0; /* set list to zero size */
ZTS_FD_ZERO(set);
fast_seq = PySequence_Fast(seq, "arguments 1-3 must be sequences");
if (! fast_seq) {
return -1;
}
for (i = 0; i < PySequence_Fast_GET_SIZE(fast_seq); i++) {
int v;
/* any intervening fileno() calls could decr this refcnt */
if (! (o = PySequence_Fast_GET_ITEM(fast_seq, i))) {
goto finally;
}
Py_INCREF(o);
v = PyObject_AsFileDescriptor(o);
if (v == -1) {
goto finally;
}
#if defined(_MSC_VER)
max = 0; /* not used for Win32 */
#else /* !_MSC_VER */
if (! _PyIsSelectable_fd(v)) {
PyErr_SetString(PyExc_ValueError, "filedescriptor out of range in select()");
goto finally;
}
if (v > max) {
max = v;
}
#endif /* _MSC_VER */
ZTS_FD_SET(v, set);
/* add object and its file descriptor to the list */
if (index >= (unsigned int)FD_SETSIZE) {
PyErr_SetString(PyExc_ValueError, "too many file descriptors in select()");
goto finally;
}
fd2obj[index].obj = o;
fd2obj[index].fd = v;
fd2obj[index].sentinel = 0;
fd2obj[++index].sentinel = -1;
}
Py_DECREF(fast_seq);
return max + 1;
finally:
Py_XDECREF(o);
Py_DECREF(fast_seq);
return -1;
}
PyObject* zts_py_select(PyObject* module, PyObject* rlist, PyObject* wlist, PyObject* xlist, PyObject* timeout_obj)
{
pylist rfd2obj[FD_SETSIZE + 1];
pylist wfd2obj[FD_SETSIZE + 1];
pylist efd2obj[FD_SETSIZE + 1];
PyObject* ret = NULL;
zts_fd_set ifdset, ofdset, efdset;
struct timeval tv, *tvp;
int imax, omax, emax, max;
int n;
_PyTime_t timeout, deadline = 0;
if (timeout_obj == Py_None) {
tvp = (struct timeval*)NULL;
}
else {
if (_PyTime_FromSecondsObject(&timeout, timeout_obj, _PyTime_ROUND_TIMEOUT) < 0) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_SetString(PyExc_TypeError, "timeout must be a float or None");
}
return NULL;
}
if (_PyTime_AsTimeval(timeout, &tv, _PyTime_ROUND_TIMEOUT) == -1) {
return NULL;
}
if (tv.tv_sec < 0) {
PyErr_SetString(PyExc_ValueError, "timeout must be non-negative");
return NULL;
}
tvp = &tv;
}
/* Convert iterables to zts_fd_sets, and get maximum fd number
* propagates the Python exception set in seq2set()
*/
rfd2obj[0].sentinel = -1;
wfd2obj[0].sentinel = -1;
efd2obj[0].sentinel = -1;
if ((imax = seq2set(rlist, &ifdset, rfd2obj)) < 0) {
goto finally;
}
if ((omax = seq2set(wlist, &ofdset, wfd2obj)) < 0) {
goto finally;
}
if ((emax = seq2set(xlist, &efdset, efd2obj)) < 0) {
goto finally;
}
max = imax;
if (omax > max) {
max = omax;
}
if (emax > max) {
max = emax;
}
if (tvp) {
deadline = _PyTime_GetMonotonicClock() + timeout;
}
do {
Py_BEGIN_ALLOW_THREADS errno = 0;
// struct zts_timeval zts_tvp;
// zts_tvp.tv_sec = tvp.tv_sec;
// zts_tvp.tv_sec = tvp.tv_sec;
n = zts_bsd_select(max, &ifdset, &ofdset, &efdset, (struct zts_timeval*)tvp);
Py_END_ALLOW_THREADS
if (errno != EINTR)
{
break;
}
/* select() was interrupted by a signal */
if (PyErr_CheckSignals()) {
goto finally;
}
if (tvp) {
timeout = deadline - _PyTime_GetMonotonicClock();
if (timeout < 0) {
/* bpo-35310: lists were unmodified -- clear them explicitly */
ZTS_FD_ZERO(&ifdset);
ZTS_FD_ZERO(&ofdset);
ZTS_FD_ZERO(&efdset);
n = 0;
break;
}
_PyTime_AsTimeval_noraise(timeout, &tv, _PyTime_ROUND_CEILING);
/* retry select() with the recomputed timeout */
}
} while (1);
#ifdef MS_WINDOWS
if (n == SOCKET_ERROR) {
PyErr_SetExcFromWindowsErr(PyExc_OSError, WSAGetLastError());
}
#else
if (n < 0) {
PyErr_SetFromErrno(PyExc_OSError);
}
#endif
else {
/* any of these three calls can raise an exception. it's more
convenient to test for this after all three calls... but
is that acceptable?
*/
rlist = set2list(&ifdset, rfd2obj);
wlist = set2list(&ofdset, wfd2obj);
xlist = set2list(&efdset, efd2obj);
if (PyErr_Occurred()) {
ret = NULL;
}
else {
ret = PyTuple_Pack(3, rlist, wlist, xlist);
}
Py_XDECREF(rlist);
Py_XDECREF(wlist);
Py_XDECREF(xlist);
}
finally:
reap_obj(rfd2obj);
reap_obj(wfd2obj);
reap_obj(efd2obj);
return ret;
}
int zts_py_setsockopt(int fd, PyObject* args)
{
int level;
int optname;
int res;
Py_buffer optval;
int flag;
unsigned int optlen;
PyObject* none;
// setsockopt(level, opt, flag)
if (PyArg_ParseTuple(args, "iii:setsockopt", &level, &optname, &flag)) {
res = zts_bsd_setsockopt(fd, level, optname, (char*)&flag, sizeof flag);
goto done;
}
PyErr_Clear();
// setsockopt(level, opt, None, flag)
if (PyArg_ParseTuple(args, "iiO!I:setsockopt", &level, &optname, Py_TYPE(Py_None), &none, &optlen)) {
assert(sizeof(socklen_t) >= sizeof(unsigned int));
res = zts_bsd_setsockopt(fd, level, optname, NULL, (socklen_t)optlen);
goto done;
}
PyErr_Clear();
// setsockopt(level, opt, buffer)
if (! PyArg_ParseTuple(args, "iiy*:setsockopt", &level, &optname, &optval))
return NULL;
#ifdef MS_WINDOWS
if (optval.len > INT_MAX) {
PyBuffer_Release(&optval);
PyErr_Format(PyExc_OverflowError, "socket option is larger than %i bytes", INT_MAX);
return NULL;
}
res = zts_bsd_setsockopt(fd, level, optname, optval.buf, (int)optval.len);
#else
res = zts_bsd_setsockopt(fd, level, optname, optval.buf, optval.len);
#endif
PyBuffer_Release(&optval);
done:
return res;
}
PyObject* zts_py_getsockopt(int fd, PyObject* args)
{
int level;
int optname;
int res;
PyObject* buf;
socklen_t buflen = 0;
int flag = 0;
socklen_t flagsize;
if (! PyArg_ParseTuple(args, "ii|i:getsockopt", &level, &optname, &buflen)) {
return NULL;
}
if (buflen == 0) {
flagsize = sizeof flag;
res = zts_bsd_getsockopt(fd, level, optname, (void*)&flag, &flagsize);
if (res < 0) {
return set_error();
}
return PyLong_FromLong(flag);
}
if (buflen <= 0 || buflen > 1024) {
PyErr_SetString(PyExc_OSError, "getsockopt buflen out of range");
return NULL;
}
buf = PyBytes_FromStringAndSize((char*)NULL, buflen);
if (buf == NULL) {
return NULL;
}
res = zts_bsd_getsockopt(fd, level, optname, (void*)PyBytes_AS_STRING(buf), &buflen);
if (res < 0) {
Py_DECREF(buf);
return set_error();
}
_PyBytes_Resize(&buf, buflen);
return buf;
}
PyObject* zts_py_fcntl(int fd, int code, PyObject* arg)
{
unsigned int int_arg = 0;
int ret;
if (PySys_Audit("fcntl.fcntl", "iiO", fd, code, arg ? arg : Py_None) < 0) {
return NULL;
}
if (arg != NULL) {
int parse_result;
PyErr_Clear();
parse_result = PyArg_Parse(
arg,
"I;fcntl requires a file or file descriptor,"
" an integer and optionally a third integer",
&int_arg);
if (! parse_result) {
return NULL;
}
}
do {
Py_BEGIN_ALLOW_THREADS ret = zts_bsd_fcntl(fd, code, (int)int_arg);
Py_END_ALLOW_THREADS
} while (ret == -1 && zts_errno == ZTS_EINTR);
if (ret < 0) {
return set_error();
}
return PyLong_FromLong((long)ret);
}
PyObject* zts_py_ioctl(int fd, unsigned int code, PyObject* ob_arg, int mutate_arg)
{
#define IOCTL_BUFSZ 1024
int arg = 0;
int ret;
Py_buffer pstr;
char* str;
Py_ssize_t len;
char buf[IOCTL_BUFSZ + 1]; /* argument plus NUL byte */
if (PySys_Audit("fcntl.ioctl", "iIO", fd, code, ob_arg ? ob_arg : Py_None) < 0) {
return NULL;
}
if (ob_arg != NULL) {
if (PyArg_Parse(ob_arg, "w*:ioctl", &pstr)) {
char* arg;
str = (char*)pstr.buf;
len = pstr.len;
if (mutate_arg) {
if (len <= IOCTL_BUFSZ) {
memcpy(buf, str, len);
buf[len] = '\0';
arg = buf;
}
else {
arg = str;
}
}
else {
if (len > IOCTL_BUFSZ) {
PyBuffer_Release(&pstr);
PyErr_SetString(PyExc_ValueError, "ioctl string arg too long");
return NULL;
}
else {
memcpy(buf, str, len);
buf[len] = '\0';
arg = buf;
}
}
if (buf == arg) {
Py_BEGIN_ALLOW_THREADS /* think array.resize() */
ret = zts_bsd_ioctl(fd, code, arg);
Py_END_ALLOW_THREADS
}
else {
ret = zts_bsd_ioctl(fd, code, arg);
}
if (mutate_arg && (len <= IOCTL_BUFSZ)) {
memcpy(str, buf, len);
}
PyBuffer_Release(&pstr); /* No further access to str below this point */
if (ret < 0) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
if (mutate_arg) {
return PyLong_FromLong(ret);
}
else {
return PyBytes_FromStringAndSize(buf, len);
}
}
PyErr_Clear();
if (PyArg_Parse(ob_arg, "s*:ioctl", &pstr)) {
str = (char*)pstr.buf;
len = pstr.len;
if (len > IOCTL_BUFSZ) {
PyBuffer_Release(&pstr);
PyErr_SetString(PyExc_ValueError, "ioctl string arg too long");
return NULL;
}
memcpy(buf, str, len);
buf[len] = '\0';
Py_BEGIN_ALLOW_THREADS ret = zts_bsd_ioctl(fd, code, buf);
Py_END_ALLOW_THREADS if (ret < 0)
{
PyBuffer_Release(&pstr);
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
PyBuffer_Release(&pstr);
return PyBytes_FromStringAndSize(buf, len);
}
PyErr_Clear();
if (! PyArg_Parse(
ob_arg,
"i;ioctl requires a file or file descriptor,"
" an integer and optionally an integer or buffer argument",
&arg)) {
return NULL;
}
// Fall-through to outside the 'if' statement.
}
// TODO: Double check that &arg is correct
Py_BEGIN_ALLOW_THREADS ret = zts_bsd_ioctl(fd, code, &arg);
Py_END_ALLOW_THREADS if (ret < 0)
{
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
return PyLong_FromLong((long)ret);
#undef IOCTL_BUFSZ
}
#endif // ZTS_ENABLE_PYTHON #endif // ZTS_ENABLE_PYTHON

View File

@@ -17,26 +17,28 @@
#ifdef ZTS_ENABLE_PYTHON #ifdef ZTS_ENABLE_PYTHON
#include "Python.h" #include "Python.h"
typedef int SOCKET_T;
int zts_py_bind(int fd, int family, int type, PyObject* addro); int zts_py_bind(int fd, int family, int type, PyObject* addro);
int zts_py_connect(int fd, int family, int type, PyObject* addro); int zts_py_connect(int fd, int family, int type, PyObject* addro);
PyObject* zts_py_accept(int fd); PyObject* zts_py_accept(int fd);
int zts_py_listen(int fd, int backlog);
PyObject* zts_py_recv(int fd, int len, int flags); PyObject* zts_py_recv(int fd, int len, int flags);
int zts_py_send(int fd, PyObject* buf, int flags); int zts_py_send(int fd, PyObject* buf, int flags);
int zts_py_close(int fd); int zts_py_close(int fd);
int zts_py_setblocking(int fd, int flag);
int zts_py_getblocking(int fd);
PyObject* zts_py_addr_get_str(uint64_t net_id, int family); PyObject* zts_py_addr_get_str(uint64_t net_id, int family);
PyObject* zts_py_select(PyObject* module, PyObject* rlist, PyObject* wlist, PyObject* xlist, PyObject* timeout_obj);
int zts_py_setsockopt(int fd, PyObject* args);
PyObject* zts_py_getsockopt(int fd, PyObject* args);
#endif // ZTS_ENABLE_PYTHON #endif // ZTS_ENABLE_PYTHON
#endif // ZTS_PYTHON_SOCKETS_H #endif // ZTS_PYTHON_SOCKETS_H

View File

@@ -76,6 +76,9 @@ class ZeroTierNode:
def node_is_online(self): def node_is_online(self):
return libzt.zts_node_is_online() return libzt.zts_node_is_online()
def node_id(self):
return libzt.zts_node_get_id()
def net_transport_is_ready(self, net_id): def net_transport_is_ready(self, net_id):
return libzt.zts_net_transport_is_ready(net_id) return libzt.zts_net_transport_is_ready(net_id)

View File

@@ -0,0 +1,7 @@
import libzt
class select:
def select(self, r, w, x, timeout=None):
r, w, x = libzt.zts_py_select(self, r, w, x, timeout)
return r, w + x, []

View File

@@ -104,13 +104,11 @@ class socket:
def fromfd(self, fd, sock_family, sock_type, sock_proto=0): def fromfd(self, fd, sock_family, sock_type, sock_proto=0):
"""libzt does not support this (yet)""" """libzt does not support this (yet)"""
raise NotImplementedError( raise NotImplementedError("ZeroTier does not expose OS-level sockets")
"fromfd(): Not supported. OS File descriptors aren't used in libzt."
)
def fromshare(self, data): def fromshare(self, data):
"""libzt does not support this (yet)""" """ZeroTier sockets cannot be shared"""
raise NotImplementedError("libzt does not support this (yet?)") raise NotImplementedError("ZeroTier sockets cannot be shared")
def getaddrinfo( def getaddrinfo(
self, host, port, sock_family=0, sock_type=0, sock_proto=0, flags=0 self, host, port, sock_family=0, sock_type=0, sock_proto=0, flags=0
@@ -246,21 +244,21 @@ class socket:
if err < 0: if err < 0:
handle_error(err) handle_error(err)
def connect(self, remote_address): def connect(self, address):
"""connect(address) """connect(address)
Connect the socket to a remote address""" Connect the socket to a remote address"""
err = libzt.zts_py_connect(self._fd, self._family, self._type, remote_address) err = libzt.zts_py_connect(self._fd, self._family, self._type, address)
if err < 0: if err < 0:
handle_error(err) handle_error(err)
def connect_ex(self, remote_address): def connect_ex(self, address):
"""connect_ex(address) -> errno """connect_ex(address) -> errno
Connect to remote host but return low-level result code, and errno on failure Connect to remote host but return low-level result code, and errno on failure
This uses a non-thread-safe implementation of errno This uses a non-thread-safe implementation of errno
""" """
err = libzt.zts_py_connect(self._fd, self._family, self._type, remote_address) err = libzt.zts_py_connect(self._fd, self._family, self._type, address)
if err < 0: if err < 0:
return errno() return errno()
return err return err
@@ -268,7 +266,7 @@ 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. ZeroTier sockets are not OS-level sockets"
) )
def dup(self): def dup(self):
@@ -276,18 +274,19 @@ class socket:
raise NotImplementedError("libzt does not support this (yet?)") raise NotImplementedError("libzt does not support this (yet?)")
def fileno(self): def fileno(self):
"""libzt does not support this""" """Return ZeroTier socket file descriptor. This is not OS-level. Can
raise NotImplementedError("libzt does not support this (yet?)") only be used with ZeroTier's version of select"""
return self._fd
def get_inheritable(self): def get_inheritable(self):
"""libzt does not support this (yet)""" """ZeroTier sockets cannot be shared. This always returns False"""
raise NotImplementedError("libzt does not support this (yet?)") return False
def getblocking(self): def getblocking(self):
"""getblocking() """getblocking()
Return True if the socket is in blocking mode, False if it is non-blocking""" Return True if the socket is in blocking mode, False if it is non-blocking"""
return libzt.zts_py_getblocking(self._fd) return libzt.zts_get_blocking(self._fd)
def getpeername(self): def getpeername(self):
"""libzt does not support this (yet)""" """libzt does not support this (yet)"""
@@ -297,17 +296,17 @@ 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 getsockopt(self, level, optname, buflen): def getsockopt(self, level, optname, buflen=None):
"""libzt does not support this (yet)""" """Get a socket option value"""
raise NotImplementedError("libzt does not support this (yet?)") return libzt.zts_py_getsockopt(self._fd, (level, optname))
def gettimeout(self): def gettimeout(self):
"""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 ioctl(self, control, option): def ioctl(self, request, arg=0, mutate_flag=True):
"""libzt does not support this (yet)""" """Perform I/O control operations"""
raise NotImplementedError("libzt does not support this (yet?)") return libzt.zts_py_ioctl(self._fd, request, arg, mutate_flag)
def listen(self, backlog=None): def listen(self, backlog=None):
"""listen([backlog]) """listen([backlog])
@@ -322,7 +321,7 @@ class socket:
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_bsd_listen(self._fd, backlog)
if err < 0: if err < 0:
handle_error(err) handle_error(err)
@@ -412,25 +411,25 @@ class socket:
raise NotImplementedError("libzt does not support this (yet?)") raise NotImplementedError("libzt does not support this (yet?)")
def set_inheritable(self, inheritable): def set_inheritable(self, inheritable):
"""libzt does not support this (yet)""" """ZeroTier sockets cannot be shared"""
raise NotImplementedError("libzt does not support this (yet?)") raise NotImplementedError("ZeroTier sockets cannot be shared")
def setblocking(self, flag): def setblocking(self, flag):
"""setblocking(flag) """setblocking(flag)
Sets the socket to blocking mode if flag=True, non-blocking if flag=False.""" Sets the socket to blocking mode if flag=True, non-blocking if flag=False."""
libzt.zts_py_setblocking(self._fd, flag) libzt.zts_set_blocking(self._fd, flag)
def settimeout(self, value): def settimeout(self, value):
"""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?)")
# TODO: value: buffer
# TODO: value: int
# TODO: value: None -> optlen required
def setsockopt(self, level, optname, value): def setsockopt(self, level, optname, value):
"""libzt does not support this (yet)""" """Set a socket option value"""
raise NotImplementedError("libzt does not support this (yet?)") err = libzt.zts_py_setsockopt(self._fd, (level, optname, value))
if err < 0:
handle_error(err)
return err
def shutdown(self, how): def shutdown(self, how):
"""shutdown(how) """shutdown(how)