"""ZeroTier low-level socket interface""" import libzt class EventCallbackClass(libzt.PythonDirectorCallbackClass): """ZeroTier event callback class""" pass def handle_error(err): """Convert libzt error code to exception""" 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) + ")") # This implementation of errno is NOT thread safe # 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 # threads making socket calls. def errno(): """Return errno value of low-level socket layer""" 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: """Pythonic class that wraps low-level sockets""" _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 is None: self._fd = libzt.zts_socket(sock_family, sock_type, sock_proto) def has_dualstack_ipv6(self): """Return whether libzt supports dual stack sockets: yes""" return True @property def family(self): """Return family of socket""" return self._family @property def type(self): """Return type of socket""" return self._type @property def proto(self): """Return protocol of socket""" return self._proto def socketpair(self, sock_family, sock_type, sock_proto): """Intentionally not supported""" raise NotImplementedError( "socketpair(): libzt does not support AF_UNIX sockets" ) def create_connection(self, remote_address): """Convenience function to create a connection to a remote host""" # TODO: implement timeout conn = socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) conn.connect(remote_address) return conn def create_server(self, local_address, sock_family=libzt.ZTS_AF_INET, backlog=None): """Convenience function to create a listening socket""" # TODO: implement reuse_port conn = socket(sock_family, libzt.ZTS_SOCK_STREAM, 0) conn.bind(local_address) conn.listen(backlog) return conn def fromfd(self, fd, sock_family, sock_type, sock_proto=0): """libzt does not support this (yet)""" raise NotImplementedError( "fromfd(): Not supported. OS File descriptors aren't used in libzt." ) def fromshare(self, data): """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): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def getfqdn(self, name): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def gethostbyname(self, hostname): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def gethostbyname_ex(self, hostname): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def gethostname(self): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def gethostbyaddr(self, ip_address): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def getnameinfo(self, sockaddr, flags): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def getprotobyname(self, protocolname): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def getservbyname(self, servicename, protocolname): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def getservbyport(self, port, protocolname): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def ntohl(x): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def ntohs(x): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def htonl(x): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def htons(x): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def inet_aton(ip_string): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def inet_ntoa(packed_ip): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def inet_pton(address_family, ip_string): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def inet_ntop(address_family, packed_ip): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def CMSG_LEN(length): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def CMSG_SPACE(length): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def getdefaulttimeout(self): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def setdefaulttimeout(self, timeout): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def sethostname(self, name): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def if_nameindex(self): """libzt does not support this""" raise NotImplementedError("if_nameindex(): libzt does not name interfaces.") def if_nametoindex(self, if_name): """libzt does not support this""" raise NotImplementedError("if_nametoindex(): libzt does not name interfaces.") def if_indextoname(self, if_index): """libzt does not support this""" raise NotImplementedError("if_indextoname(): libzt does not name interfaces.") def accept(self): """accept() -> (socket, address_info) Wait for incoming connection and return a tuple of socket object and client address info. This address info may be a tuple of host address and port.""" new_conn_fd, addr, port = libzt.zts_py_accept(self._fd) if new_conn_fd < 0: handle_error(new_conn_fd) return None return socket(self._family, self._type, self._proto, new_conn_fd), addr def bind(self, local_address): """bind(address) Bind the socket to a local interface address""" err = libzt.zts_py_bind(self._fd, self._family, self._type, local_address) if err < 0: handle_error(err) def close(self): """close() Close the socket""" err = libzt.zts_py_close(self._fd) if err < 0: handle_error(err) def connect(self, remote_address): """connect(address) Connect the socket to a remote address""" err = libzt.zts_py_connect(self._fd, self._family, self._type, remote_address) if err < 0: handle_error(err) def connect_ex(self, remote_address): """connect_ex(address) -> errno Connect to remote host but return low-level result code, and errno on failure This uses a non-thread-safe implementation of errno """ err = libzt.zts_py_connect(self._fd, self._family, self._type, remote_address) if err < 0: return errno() return err def detach(self): """libzt does not support this""" raise NotImplementedError( "detach(): Not supported. OS File descriptors aren't used in libzt.") def dup(self): """libzt does not support this""" raise NotImplementedError("libzt does not support this (yet?)") def fileno(self): """libzt does not support this""" raise NotImplementedError("libzt does not support this (yet?)") def get_inheritable(self): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def getblocking(self): """getblocking() Return True if the socket is in blocking mode, False if it is non-blocking""" return libzt.zts_py_getblocking(self._fd) def getpeername(self): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def getsockname(self): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def getsockopt(self, level, optname, buflen): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def gettimeout(self): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def ioctl(self, control, option): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def listen(self, backlog=None): """listen([backlog]) Put the socket in a listening state. Backlog specifies the number of outstanding connections the OS will queue without being accepted. If less than 0, it is set to 0. If not specified, a reasonable default will be used.""" if backlog is not None and backlog < 0: backlog = 0 if backlog is None: backlog = -1 # Lower-level code picks default err = libzt.zts_py_listen(self._fd, backlog) if err < 0: handle_error(err) def makefile(mode="r", buffering=None, *, encoding=None, errors=None, newline=None): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def recv(self, n_bytes, flags=0): """recv(buffersize[, flags]) -> data Read up to buffersize bytes from remote. Wait until at least one byte is read, or remote is closed. If all data is read and remote is closed, returns empty string. Flags may be: - ZTS_MSG_PEEK - Peeks at an incoming message. - ZTS_MSG_DONTWAIT - Nonblocking I/O for this operation only. - ZTS_MSG_MORE - Wait for more than one message. """ err, data = libzt.zts_py_recv(self._fd, n_bytes, flags) if err < 0: handle_error(err) return None return data def recvfrom(self, bufsize, flags): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def recvmsg(self, bufsize, ancbufsize, flags): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def recvmsg_into(self, buffers, ancbufsize, flags): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def recvfrom_into(self, buffer, n_bytes, flags): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def recv_into(self, buffer, n_bytes, flags): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def send(self, data, flags=0): """send(data[, flags]) -> count Write data to the socket. Returns the number of bytes sent, which may be less than the full data size if the network is busy. Optional flags may be: - ZTS_MSG_PEEK - Peeks at an incoming message. - ZTS_MSG_DONTWAIT - Nonblocking I/O for this operation only. - ZTS_MSG_MORE - Sender will send more. """ err = libzt.zts_py_send(self._fd, data, flags) if err < 0: handle_error(err) return err def sendall(self, n_bytes, flags): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def sendto(self, n_bytes, flags, address): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def sendmsg(self, buffers, ancdata, flags, address): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def sendmsg_afalg(self, msg, *, op, iv, assoclen, flags): """libzt does not support this (yet)""" raise NotImplementedError("sendmsg_afalg(): libzt does not support AF_ALG") def send_fds(self, sock, buffers, fds, flags, address): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def recv_fds(self, sock, bufsize, maxfds, flags): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def sendfile(self, file, offset=0, count=None): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def set_inheritable(self, inheritable): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def setblocking(self, flag): """setblocking(flag) Sets the socket to blocking mode if flag=True, non-blocking if flag=False.""" libzt.zts_py_setblocking(self._fd, flag) def settimeout(self, value): """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): """libzt does not support this (yet)""" raise NotImplementedError("libzt does not support this (yet?)") def shutdown(self, how): """shutdown(how) Shut down one or more aspects (rx/tx) of the socket depending on how: - ZTS_SHUT_RD - Shut down reading side of socket. - ZTS_SHUT_WR - Shut down writing side of socket. - ZTS_SHUT_RDWR - Both ends of the socket. """ libzt.zts_shutdown(self._fd, how)