From a6fc3e44b881cd6062a86bedd1d7c26951faa7b3 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Wed, 10 Feb 2021 02:26:01 -0800 Subject: [PATCH 01/25] Remove WIP Java example wrapper --- src/java/ZeroTier.java | 248 -------- src/java/ZeroTierEventListener.java | 9 - src/java/ZeroTierFileDescriptorSet.java | 28 - src/java/ZeroTierInputStream.java | 209 ------- src/java/ZeroTierIoctlArg.java | 6 - src/java/ZeroTierOutputStream.java | 81 --- src/java/ZeroTierPeerDetails.java | 47 -- src/java/ZeroTierProtoStats.java | 19 - src/java/ZeroTierSSLSocketFactory.java | 101 ---- src/java/ZeroTierSocket.java | 748 ----------------------- src/java/ZeroTierSocketAddress.java | 66 -- src/java/ZeroTierSocketFactory.java | 51 -- src/java/ZeroTierSocketImpl.java | 771 ------------------------ src/java/ZeroTierSocketImplFactory.java | 29 - src/java/ZeroTierSocketOptionValue.java | 14 - 15 files changed, 2427 deletions(-) delete mode 100644 src/java/ZeroTier.java delete mode 100644 src/java/ZeroTierEventListener.java delete mode 100644 src/java/ZeroTierFileDescriptorSet.java delete mode 100644 src/java/ZeroTierInputStream.java delete mode 100644 src/java/ZeroTierIoctlArg.java delete mode 100644 src/java/ZeroTierOutputStream.java delete mode 100644 src/java/ZeroTierPeerDetails.java delete mode 100644 src/java/ZeroTierProtoStats.java delete mode 100644 src/java/ZeroTierSSLSocketFactory.java delete mode 100644 src/java/ZeroTierSocket.java delete mode 100644 src/java/ZeroTierSocketAddress.java delete mode 100644 src/java/ZeroTierSocketFactory.java delete mode 100644 src/java/ZeroTierSocketImpl.java delete mode 100644 src/java/ZeroTierSocketImplFactory.java delete mode 100644 src/java/ZeroTierSocketOptionValue.java diff --git a/src/java/ZeroTier.java b/src/java/ZeroTier.java deleted file mode 100644 index 7015c73..0000000 --- a/src/java/ZeroTier.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (c)2013-2020 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: 2024-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. - */ -/****/ - -package com.zerotier.libzt; - -import java.net.*; - -public class ZeroTier -{ - ////////////////////////////////////////////////////////////////////////////// - // Control API error codes // - ////////////////////////////////////////////////////////////////////////////// - - // Everything is ok - public static int ZTS_ERR_OK = 0; - // Error - public static int ZTS_ERR = -1; - // A argument provided by the user application is invalid (e.g. out of range, NULL, etc) - public static int ZTS_ERR_INVALID_ARG = -2; - // The service isn't initialized or is for some reason currently unavailable. Try again. - public static int ZTS_ERR_SERVICE = -3; - // For some reason this API operation is not permitted or doesn't make sense at this time. - public static int ZTS_ERR_INVALID_OP = -4; - // The call succeeded, but no object or relevant result was available - public static int ZTS_ERR_NO_RESULT = -5; - // General internal failure - public static int ZTS_ERR_GENERAL = -6; - - ////////////////////////////////////////////////////////////////////////////// - // Static initialization // - ////////////////////////////////////////////////////////////////////////////// - - static - { - // loads libzt.so or libzt.dylib - System.loadLibrary("zt"); - // Give the native code a reference to this VM (for callbacks) - if (init() != ZTS_ERR_OK) { - throw new ExceptionInInitializerError("JNI init() failed (see GetJavaVM())"); - } - } - - ////////////////////////////////////////////////////////////////////////////// - // Control API event codes // - ////////////////////////////////////////////////////////////////////////////// - - public static int EVENT_NONE = -1; - // Node-specific events - public static int EVENT_NODE_UP = 0; - public static int EVENT_NODE_OFFLINE = 1; - public static int EVENT_NODE_ONLINE = 2; - public static int EVENT_NODE_DOWN = 3; - public static int EVENT_NODE_IDENTITY_COLLISION = 4; - // libzt node events - public static int EVENT_NODE_UNRECOVERABLE_ERROR = 16; - public static int EVENT_NODE_NORMAL_TERMINATION = 17; - // Network-specific events - public static int EVENT_NETWORK_NOT_FOUND = 32; - public static int EVENT_NETWORK_CLIENT_TOO_OLD = 33; - public static int EVENT_NETWORK_REQUESTING_CONFIG = 34; - public static int EVENT_NETWORK_OK = 35; - public static int EVENT_NETWORK_ACCESS_DENIED = 36; - public static int EVENT_NETWORK_READY_IP4 = 37; - public static int EVENT_NETWORK_READY_IP6 = 38; - public static int EVENT_NETWORK_READY_IP4_IP6 = 39; - public static int EVENT_NETWORK_DOWN = 40; - // lwIP netif events - public static int EVENT_NETIF_UP_IP4 = 64; - public static int EVENT_NETIF_UP_IP6 = 65; - public static int EVENT_NETIF_DOWN_IP4 = 66; - public static int EVENT_NETIF_DOWN_IP6 = 67; - public static int EVENT_NETIF_REMOVED = 68; - public static int EVENT_NETIF_LINK_UP = 69; - public static int EVENT_NETIF_LINK_DOWN = 70; - public static int EVENT_NETIF_NEW_ADDRESS = 71; - // Peer events - public static int EVENT_PEER_P2P = 96; - public static int EVENT_PEER_RELAY = 97; - public static int EVENT_PEER_UNREACHABLE = 98; - - ////////////////////////////////////////////////////////////////////////////// - // ZeroTier Constants // - ////////////////////////////////////////////////////////////////////////////// - - public static int ZT_MAX_PEER_NETWORK_PATHS = 16; - - ////////////////////////////////////////////////////////////////////////////// - // Socket API Constants // - ////////////////////////////////////////////////////////////////////////////// - - // Socket protocol types - public static int SOCK_STREAM = 0x00000001; - public static int SOCK_DGRAM = 0x00000002; - public static int SOCK_RAW = 0x00000003; - // Socket family types - public static int AF_INET = 0x00000002; - public static int AF_INET6 = 0x0000000a; - public static int PF_INET = AF_INET; - public static int PF_INET6 = AF_INET6; - // Used as level numbers for setsockopt() and getsockopt() - public static int IPPROTO_IP = 0x00000000; - public static int IPPROTO_ICMP = 0x00000001; - public static int IPPROTO_TCP = 0x00000006; - public static int IPPROTO_UDP = 0x00000011; - public static int IPPROTO_IPV6 = 0x00000029; - public static int IPPROTO_ICMPV6 = 0x0000003a; - public static int IPPROTO_UDPLITE = 0x00000088; - public static int IPPROTO_RAW = 0x000000ff; - // send() and recv() flags - public static int MSG_PEEK = 0x00000001; - public static int MSG_WAITALL = 0x00000002; - public static int MSG_OOB = 0x00000004; - public static int MSG_DONTWAIT = 0x00000008; - public static int MSG_MORE = 0x00000010; - // fnctl() commands - public static int F_GETFL = 0x00000003; - public static int F_SETFL = 0x00000004; - // fnctl() flags - public static int O_NONBLOCK = 0x00000001; - public static int O_NDELAY = 0x00000001; - // Shutdown commands - public static int SHUT_RD = 0x00000000; - public static int SHUT_WR = 0x00000001; - public static int SHUT_RDWR = 0x00000002; - // ioctl() commands - public static int FIONREAD = 0x4008667F; - public static int FIONBIO = 0x8008667E; - // Socket level option number - public static int SOL_SOCKET = 0x00000FFF; - // Socket options - public static int SO_REUSEADDR = 0x00000004; - public static int SO_KEEPALIVE = 0x00000008; - public static int SO_BROADCAST = 0x00000020; - // Socket options - public static int SO_DEBUG = 0x00000001; // NOT YET SUPPORTED - public static int SO_ACCEPTCONN = 0x00000002; - public static int SO_DONTROUTE = 0x00000010; // NOT YET SUPPORTED - public static int SO_USELOOPBACK = 0x00000040; // NOT YET SUPPORTED - public static int SO_LINGER = 0x00000080; - public static int SO_DONTLINGER = ((int)(~SO_LINGER)); - public static int SO_OOBINLINE = 0x00000100; // NOT YET SUPPORTED - public static int SO_REUSEPORT = 0x00000200; // NOT YET SUPPORTED - public static int SO_SNDBUF = 0x00001001; // NOT YET SUPPORTED - public static int SO_RCVBUF = 0x00001002; - public static int SO_SNDLOWAT = 0x00001003; // NOT YET SUPPORTED - public static int SO_RCVLOWAT = 0x00001004; // NOT YET SUPPORTED - public static int SO_SNDTIMEO = 0x00001005; - public static int SO_RCVTIMEO = 0x00001006; - public static int SO_ERROR = 0x00001007; - public static int SO_TYPE = 0x00001008; - public static int SO_CONTIMEO = 0x00001009; // NOT YET SUPPORTED - public static int SO_NO_CHECK = 0x0000100a; - // IPPROTO_IP options - public static int IP_TOS = 0x00000001; - public static int IP_TTL = 0x00000002; - // IPPROTO_TCP options - public static int TCP_NODELAY = 0x00000001; - public static int TCP_KEEPALIVE = 0x00000002; - public static int TCP_KEEPIDLE = 0x00000003; - public static int TCP_KEEPINTVL = 0x00000004; - public static int TCP_KEEPCNT = 0x00000005; - - ////////////////////////////////////////////////////////////////////////////// - // Statistics // - ////////////////////////////////////////////////////////////////////////////// - - public static int STATS_PROTOCOL_LINK = 0; - public static int STATS_PROTOCOL_ETHARP = 1; - public static int STATS_PROTOCOL_IP = 2; - public static int STATS_PROTOCOL_UDP = 3; - public static int STATS_PROTOCOL_TCP = 4; - public static int STATS_PROTOCOL_ICMP = 5; - public static int STATS_PROTOCOL_IP_FRAG = 6; - public static int STATS_PROTOCOL_IP6 = 7; - public static int STATS_PROTOCOL_ICMP6 = 8; - public static int STATS_PROTOCOL_IP6_FRAG = 9; - - public static native int get_protocol_stats(int protocolNum, ZeroTierProtoStats stats); - - ////////////////////////////////////////////////////////////////////////////// - // ZeroTier Service Controls // - ////////////////////////////////////////////////////////////////////////////// - - public static native int start(String path, ZeroTierEventListener callbackClass, int port); - public static native int stop(); - public static native int restart(); - public static native int join(long nwid); - public static native int leave(long nwid); - public static native long get_node_id(); - public static native int get_num_assigned_addresses(long nwid); - public static native void get_6plane_addr(long nwid, long nodeId, ZeroTierSocketAddress addr); - public static native void get_rfc4193_addr(long nwid, long nodeId, ZeroTierSocketAddress addr); - public static native int get_node_status(); - public static native int get_network_status(long networkId); - public static native int get_peer_status(long peerId); - public static native int get_peer(long peerId, ZeroTierPeerDetails details); - - ////////////////////////////////////////////////////////////////////////////// - // Socket API // - ////////////////////////////////////////////////////////////////////////////// - - public static native int socket(int family, int type, int protocol); - public static native int connect(int fd, ZeroTierSocketAddress addr); - public static native int bind(int fd, ZeroTierSocketAddress addr); - public static native int listen(int fd, int backlog); - public static native int accept(int fd, ZeroTierSocketAddress addr); - public static native int accept4(int fd, String addr, int port); - - public static native int setsockopt(int fd, int level, int optname, ZeroTierSocketOptionValue optval); - public static native int getsockopt(int fd, int level, int optname, ZeroTierSocketOptionValue optval); - - public static native int read(int fd, byte[] buf); - public static native int read_offset(int fd, byte[] buf, int offset, int len); - public static native int read_length(int fd, byte[] buf, int len); - public static native int recv(int fd, byte[] buf, int flags); - public static native int recvfrom(int fd, byte[] buf, int flags, ZeroTierSocketAddress addr); - - public static native int write(int fd, byte[] buf); - public static native int write_byte(int fd, byte b); - public static native int write_offset(int fd, byte[] buf, int offset, int len); - public static native int sendto(int fd, byte[] buf, int flags, ZeroTierSocketAddress addr); - public static native int send(int fd, byte[] buf, int flags); - - public static native int shutdown(int fd, int how); - public static native int close(int fd); - - public static native boolean getsockname(int fd, ZeroTierSocketAddress addr); - public static native int getpeername(int fd, ZeroTierSocketAddress addr); - public static native int fcntl(int sock, int cmd, int flag); - public static native int ioctl(int fd, long request, ZeroTierIoctlArg arg); - public static native int select(int nfds, ZeroTierFileDescriptorSet readfds, ZeroTierFileDescriptorSet writefds, ZeroTierFileDescriptorSet exceptfds, int timeout_sec, int timeout_usec); - - ////////////////////////////////////////////////////////////////////////////// - // Internal - Do not call // - ////////////////////////////////////////////////////////////////////////////// - - public static native int init(); // Only to be called by static initializer of this class -} diff --git a/src/java/ZeroTierEventListener.java b/src/java/ZeroTierEventListener.java deleted file mode 100644 index fd5e54e..0000000 --- a/src/java/ZeroTierEventListener.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.zerotier.libzt; - -public interface ZeroTierEventListener { - - /* - * Called when an even occurs in the native section of the ZeroTier library service - */ - public void onZeroTierEvent(long nwid, int eventCode); -} \ No newline at end of file diff --git a/src/java/ZeroTierFileDescriptorSet.java b/src/java/ZeroTierFileDescriptorSet.java deleted file mode 100644 index 000902b..0000000 --- a/src/java/ZeroTierFileDescriptorSet.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.zerotier.libzt; - -public class ZeroTierFileDescriptorSet -{ - byte[] fds_bits = new byte[1024]; - - public void CLR(int fd) - { - fds_bits[fd] = 0x00; - } - - public boolean ISSET(int fd) - { - return fds_bits[fd] == 0x01; - } - - public void SET(int fd) - { - fds_bits[fd] = 0x01; - } - - public void ZERO() - { - for (int i=0; i<1024; i++) { - fds_bits[i] = 0x00; - } - } -} \ No newline at end of file diff --git a/src/java/ZeroTierInputStream.java b/src/java/ZeroTierInputStream.java deleted file mode 100644 index 7a02d3a..0000000 --- a/src/java/ZeroTierInputStream.java +++ /dev/null @@ -1,209 +0,0 @@ -package com.zerotier.libzt; - -import com.zerotier.libzt.ZeroTier; - -import java.io.*; -import java.util.Objects; - -public class ZeroTierInputStream extends InputStream -{ - private static final int MAX_SKIP_BUFFER_SIZE = 2048; - private static final int DEFAULT_BUFFER_SIZE = 8192; - - /* - * File descriptor used by lower native layer - */ - int zfd; - - /* - * - */ - public int available() - throws IOException - { - return 0; // NOT YET SUPPORTED - } - - /* - * - */ - public void close​() - throws IOException - { - /* Note: this operation currently only stops RX on a socket that is shared - between both I/OStreams. This means that closing this stream will only shutdown - that aspect of the socket but not actually close it and free resources. Resources - will be properly freed when the socket implementation's close() is called or if - both I/OStreams are closed separately */ - ZeroTier.shutdown(zfd, ZeroTier.SHUT_RD); - zfd = -1; - } - - /* - * - */ - public void mark​(int readlimit) - { - System.err.println("mark​: ZeroTierInputStream does not currently support this feature"); - } - - /* - * - */ - public void reset​() - throws IOException - { - System.err.println("reset​: ZeroTierInputStream does not currently support this feature"); - } - - /* - * - */ - public boolean markSupported​() - { - return false; // mark() is not supported - } - - /* - * - */ - public long transferTo​(OutputStream out) - throws IOException - { - Objects.requireNonNull(out, "out must not be null"); - int bytesTransferred = 0, bytesRead; - byte[] buf = new byte[DEFAULT_BUFFER_SIZE]; - while (((bytesRead = ZeroTier.read(zfd, buf)) >= 0)) { - out.write(buf, 0, bytesRead); - bytesTransferred += bytesRead; - } - return bytesTransferred; - } - - /* - * - */ - public int read​() - throws IOException - { - byte[] buf = new byte[1]; - // Unlike a native read(), if nothing is read we should return -1 - int retval = ZeroTier.read(zfd, buf); - if ((retval == 0) | (retval == -104) /* EINTR, from SO_RCVTIMEO */) { - return -1; - } - if (retval < 0) { - throw new IOException("read​(), errno="+retval); - } - return buf[0]; - } - - /* - * - */ - public int read​(byte[] b) - throws IOException - { - Objects.requireNonNull(b, "input byte array must not be null"); - // Unlike a native read(), if nothing is read we should return -1 - int retval = ZeroTier.read(zfd, b); - if ((retval == 0) | (retval == -104) /* EINTR, from SO_RCVTIMEO */) { - return -1; - } - if (retval < 0) { - throw new IOException("read​(b), errno="+retval); - } - return retval; - } - - /* - * - */ - public int read​(byte[] b, int off, int len) - throws IOException - { - Objects.requireNonNull(b, "input byte array must not be null"); - if ((off < 0) | (len < 0) | (len > b.length - off)) { - throw new IndexOutOfBoundsException("invalid argument"); - } - if (len == 0) { - return 0; - } - // Unlike a native read(), if nothing is read we should return -1 - int retval = ZeroTier.read_offset(zfd, b, off, len); - if ((retval == 0) | (retval == -104) /* EINTR, from SO_RCVTIMEO */) { - return -1; - } - if (retval < 0) { - throw new IOException("read​(b,off,len), errno="+retval); - } - //System.out.println("readNBytes​(byte[] b, int off="+off+", int len="+len+")="+retval); - return retval; - } - - /* - * - */ - public byte[] readAllBytes​() - throws IOException - { - //System.out.println("readAllBytes​()"); - ZeroTierIoctlArg ztarg = new ZeroTierIoctlArg(); - int err = ZeroTier.ioctl(zfd, ZeroTier.FIONREAD, ztarg); - byte[] buf = new byte[ztarg.integer]; - int retval = ZeroTier.read(zfd, buf); - if ((retval == 0) | (retval == -104) /* EINTR, from SO_RCVTIMEO */) { - // No action needed - } - if (retval < 0) { - throw new IOException("readAllBytes​(b,off,len), errno="+retval); - } - return buf; - } - - /* - * - */ - public int readNBytes​(byte[] b, int off, int len) - throws IOException - { - Objects.requireNonNull(b, "input byte array must not be null"); - if ((off < 0) | (len < 0) | (len > b.length - off)) { - throw new IndexOutOfBoundsException("invalid argument"); - } - if (len == 0) { - return 0; - } - int retval = ZeroTier.read_offset(zfd, b, off, len); - if ((retval == 0) | (retval == -104) /* EINTR, from SO_RCVTIMEO */) { - // No action needed - } - if (retval < 0) { - throw new IOException("readNBytes​(b,off,len), errno="+retval); - } - //System.out.println("readNBytes​(byte[] b, int off="+off+", int len="+len+")="+retval); - return retval; - } - - /* - * - */ - public long skip​(long n) - throws IOException - { - //System.out.println("skip()"); - if (n <= 0) { - return 0; - } - long bytesRemaining = n, bytesRead; - int bufSize = (int)Math.min(MAX_SKIP_BUFFER_SIZE, bytesRemaining); - byte[] buf = new byte[bufSize]; - while (bytesRemaining > 0) { - if ((bytesRead = ZeroTier.read_length(zfd, buf, (int)Math.min(bufSize, bytesRemaining))) < 0) { - break; - } - bytesRemaining -= bytesRead; - } - return n - bytesRemaining; // skipped - } -} \ No newline at end of file diff --git a/src/java/ZeroTierIoctlArg.java b/src/java/ZeroTierIoctlArg.java deleted file mode 100644 index 54c6131..0000000 --- a/src/java/ZeroTierIoctlArg.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.zerotier.libzt; - -public class ZeroTierIoctlArg -{ - int integer; // General integer to be used or updated by the zts_ioctl() call -} \ No newline at end of file diff --git a/src/java/ZeroTierOutputStream.java b/src/java/ZeroTierOutputStream.java deleted file mode 100644 index dd39a8a..0000000 --- a/src/java/ZeroTierOutputStream.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.zerotier.libzt; - -import com.zerotier.libzt.ZeroTier; - -import java.io.*; -import java.util.Arrays; -import java.util.Objects; - -class ZeroTierOutputStream extends OutputStream -{ - /* - * File descriptor used by lower native layer - */ - int zfd; - - /* - * - */ - public void flush() - throws IOException - { - // System.err.println("flush: ZeroTierOutputStream does not currently support this feature"); - // Not fully supported since we don't maintain a buffer - } - - /* - * - */ - public void close() - throws IOException - { - /* Note: this operation currently only stops RX on a socket that is shared - between both I/OStreams. This means that closing this stream will only shutdown - that aspect of the socket but not actually close it and free resources. Resources - will be properly freed when the socket implementation's close() is called or if - both I/OStreams are closed separately */ - ZeroTier.shutdown(zfd, ZeroTier.SHUT_WR); - zfd = -1; - } - - /* - * - */ - public void write(byte[] b) - throws IOException - { - int err = ZeroTier.write(zfd, b); - if (err < 0) { - throw new IOException("write(b[]), errno="+err); - } - } - - /* - * - */ - public void write(byte[] b, int off, int len) - throws IOException - { - Objects.requireNonNull(b, "input byte array must not be null"); - if ((off < 0) | (len < 0) | (off+len > b.length)) { - throw new IndexOutOfBoundsException("write(b,off,len)"); - } - int err = ZeroTier.write_offset(zfd, b, off, len); - if (err < 0) { - throw new IOException("write(b[],off,len), errno="+err); - } - } - - /* - * - */ - public void write(int b) - throws IOException - { - byte lowByte = (byte)(b & 0xFF); - int err = ZeroTier.write_byte(zfd, lowByte); - if (err < 0) { - throw new IOException("write(b), errno="+err); - } - } -} \ No newline at end of file diff --git a/src/java/ZeroTierPeerDetails.java b/src/java/ZeroTierPeerDetails.java deleted file mode 100644 index 49e8383..0000000 --- a/src/java/ZeroTierPeerDetails.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.zerotier.libzt; - -import com.zerotier.libzt.ZeroTier; -import com.zerotier.libzt.ZeroTierSocketAddress; - -public class ZeroTierPeerDetails -{ - /** - * ZeroTier address (40 bits) - */ - public long address; - - /** - * Remote major version or -1 if not known - */ - public int versionMajor; - - /** - * Remote minor version or -1 if not known - */ - public int versionMinor; - - /** - * Remote revision or -1 if not known - */ - public int versionRev; - - /** - * Last measured latency in milliseconds or -1 if unknown - */ - public int latency; - - /** - * What trust hierarchy role does this device have? - */ - public int role; - - /** - * Number of paths (size of paths[]) - */ - public int pathCount; - - /** - * Known network paths to peer - */ - public ZeroTierSocketAddress[] paths = new ZeroTierSocketAddress[ZeroTier.ZT_MAX_PEER_NETWORK_PATHS]; -} \ No newline at end of file diff --git a/src/java/ZeroTierProtoStats.java b/src/java/ZeroTierProtoStats.java deleted file mode 100644 index 6070dd0..0000000 --- a/src/java/ZeroTierProtoStats.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.zerotier.libzt; - -import com.zerotier.libzt.ZeroTier; - -public class ZeroTierProtoStats -{ - public int xmit; /* Transmitted packets. */ - public int recv; /* Received packets. */ - public int fw; /* Forwarded packets. */ - public int drop; /* Dropped packets. */ - public int chkerr; /* Checksum error. */ - public int lenerr; /* Invalid length error. */ - public int memerr; /* Out of memory error. */ - public int rterr; /* Routing error. */ - public int proterr; /* Protocol error. */ - public int opterr; /* Error in options. */ - public int err; /* Misc error. */ - public int cachehit; -} \ No newline at end of file diff --git a/src/java/ZeroTierSSLSocketFactory.java b/src/java/ZeroTierSSLSocketFactory.java deleted file mode 100644 index 2930f7a..0000000 --- a/src/java/ZeroTierSSLSocketFactory.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.zerotier.libzt; - -import com.zerotier.libzt.ZeroTierSocket; - -import java.net.*; -import javax.net.SocketFactory; -import java.io.IOException; -import java.io.InputStream; -import java.security.*; -import java.util.Locale; - -import javax.net.ssl.SSLSocketFactory; - -public class ZeroTierSSLSocketFactory extends SSLSocketFactory -{ - private final SSLSocketFactory delegate; - - /* - * - */ - public ZeroTierSSLSocketFactory(SSLSocketFactory delegate) - { - this.delegate = delegate; - } - - /* - * - */ - public Socket createSocket(Socket s, String host, int port, boolean autoClose) - throws IOException - { - ZeroTierSocket zs = new ZeroTierSocket(); - zs.connect((SocketAddress)new InetSocketAddress(host, port), 10); - return delegate.createSocket(zs, host, port, autoClose); - } - - /* - * - */ - public Socket createSocket(Socket s, InputStream consumed, boolean autoClose) - throws IOException - { - throw new UnsupportedOperationException(); - } - - /* - * - */ - public Socket createSocket(InetAddress a,int b,InetAddress c,int d) - throws IOException - { - ZeroTierSocket s = new ZeroTierSocket(); - return delegate.createSocket(a, b, c, d); - } - - /* - * - */ - public Socket createSocket(InetAddress a,int b) - throws IOException - { - ZeroTierSocket s = new ZeroTierSocket(); - return delegate.createSocket(a, b); - } - - /* - * - */ - public Socket createSocket(String a,int b,InetAddress c,int d) - throws IOException - { - ZeroTierSocket s = new ZeroTierSocket(); - return delegate.createSocket(a, b, c, d); - } - - /* - * - */ - public Socket createSocket(String a,int b) - throws IOException - { - ZeroTierSocket s = new ZeroTierSocket(); - return delegate.createSocket(a, b); - } - - /* - * - */ - public String [] getSupportedCipherSuites() - { - return new String[0]; - } - - /* - * - */ - public String [] getDefaultCipherSuites() - { - return new String[0]; - } -} diff --git a/src/java/ZeroTierSocket.java b/src/java/ZeroTierSocket.java deleted file mode 100644 index 4ccebb5..0000000 --- a/src/java/ZeroTierSocket.java +++ /dev/null @@ -1,748 +0,0 @@ -package com.zerotier.libzt; - -import com.zerotier.libzt.ZeroTier; -import com.zerotier.libzt.ZeroTierSocketAddress; -import com.zerotier.libzt.ZeroTierSocketImpl; -import com.zerotier.libzt.ZeroTierSocketImplFactory; -import com.zerotier.libzt.ZeroTierInputStream; -import com.zerotier.libzt.ZeroTierOutputStream; - -import java.io.*; -import java.net.*; -import java.util.Objects; -import java.nio.channels.SocketChannel; -import java.net.InetAddress; - -public class ZeroTierSocket extends Socket -{ - /* - * Factory designated to create the underlying ZeroTierSocket implementation - */ - static ZeroTierSocketImplFactory factory = new ZeroTierSocketImplFactory(); - - /* - * Underlying implementation of this ZeroTierSocket - */ - ZeroTierSocketImpl impl; - - /* - * Misc. state flags - */ - private boolean created = false; - private boolean closed = false; - private boolean connected = false; - private boolean bound = false; - private boolean inputShutdown = false; - private boolean outputShutdown = false; - - /* - * Creates and sets the implementation - */ - void setImpl() - { - if (factory != null) { - impl = factory.createSocketImpl(); - } - if (impl != null) { - impl.setSocket(this); - } - } - - /* - * Returns the underlying socket implementation - */ - private ZeroTierSocketImpl getImpl() - throws SocketException - { - if (!created) { - try { - impl.create(true); - } - catch (IOException ex) { - throw (SocketException) new SocketException().initCause(ex); - } - created = true; - } - return impl; - } - - /* - * Create the underlying socket implementation - */ - void createImpl(boolean stream) - throws SocketException - { - if (impl == null) { - setImpl(); - } - try { - impl.create(stream); - created = true; - } - catch (IOException ex) { - throw new SocketException(ex.getMessage()); - } - } - - /* - * Constructor for ZeroTierSocket - */ - public ZeroTierSocket() - throws IOException - { - this((InetAddress)null, 0, null, 0); - } - - /* - * Creates an unconnected socket - */ - protected ZeroTierSocket(ZeroTierSocketImpl impl) - throws SocketException - { - this.impl = impl; - if (impl != null) { - this.impl.setSocket(this); - } - } - - /* - * Constructor for ZeroTierSocket - */ - public ZeroTierSocket(InetAddress raddr, int rport, InetAddress laddr, int lport) - throws IOException - { - setImpl(); - - try { - if (laddr != null) { - bind(new InetSocketAddress(laddr, lport)); - } - if (raddr != null) { - connect(new InetSocketAddress(raddr, rport)); - } - } - catch (Exception ex) - { - try { - close(); - } - catch (IOException _ex) { - ex.addSuppressed(_ex); - } - throw ex; - } - } - - /* - * Constructor for ZeroTierSocket - */ - public ZeroTierSocket(InetAddress address, int port) - throws IOException - { - this(address, port, null, 0); - } - - /* - * Constructor for ZeroTierSocket - */ - public ZeroTierSocket(String address, int port) - throws IOException - { - this(InetAddress.getByName(address), port, null, 0); - } - - /* - * Constructor for ZeroTierSocket - */ - public ZeroTierSocket(String address, int port, InetAddress localHost, int localPort) - throws IOException - { - this(InetAddress.getByName(address), port, localHost, localPort); - } - - /* - * Binds the socket to a local address - */ - public void bind(SocketAddress localAddr) - throws IOException - { - if (isSocketBound()) { - throw new SocketException("bind: ZeroTierSocket is already bound"); - } - if (isSocketClosed()) { - throw new SocketException("bind: ZeroTierSocket is closed"); - } - if (localAddr != null && (!(localAddr instanceof InetSocketAddress))) { - throw new IllegalArgumentException("bind: Unsupported address type"); - } - InetSocketAddress addr = (InetSocketAddress)localAddr; - if (addr != null && addr.isUnresolved()) { - throw new SocketException("bind: Unresolved address"); - } - if (addr == null) { - addr = new InetSocketAddress(0); - } - getImpl().bind(addr.getAddress(), addr.getPort()); - bound = true; - } - - /* - * Closes the socket - */ - public synchronized void close() - throws IOException - { - if (isSocketClosed()) { - return; - } - getOutputStream().flush(); - impl.close(); - closed = true; - } - - /* - * Connects the socket to a remote address - */ - public void connect(SocketAddress remoteAddr) - throws IOException - { - connect(remoteAddr, 0); - } - - /* - * Connects the socket to a remote address - */ - public void connect(SocketAddress remoteAddr, int timeout) - throws IOException - { - if (isSocketClosed()) { - throw new SocketException("connect: ZeroTierSocket is closed"); - } - if (isSocketConnected()) { - throw new SocketException("connect: already connected"); - } - if (remoteAddr == null) { - throw new IllegalArgumentException("connect: The address can't be null"); - } - if (!(remoteAddr instanceof InetSocketAddress)) { - throw new IllegalArgumentException("connect: Unsupported address type"); - } - if (timeout < 0) { - throw new IllegalArgumentException("connect: timeout cannot be negative"); - } - if (!created) { - createImpl(true); - } - getImpl().connect(remoteAddr, 0); - bound = true; - connected = true; - } - - /* - * Returns the associated Channel - */ - public SocketChannel getChannel​() - { - System.err.println("getChannel​: ZeroTierSocket does not currently support this feature"); - return null; - } - - /* - * Returns the address to which the socket is connected - */ - public InetAddress getInetAddress​() - { - if (!isSocketConnected()) { - return null; - } - try { - return getImpl().getInetAddress(); - } - catch (SocketException ex) { - // Not Reachable - } - return null; - } - - /* - * Returns the input stream - */ - public ZeroTierInputStream getInputStream() - throws IOException - { - if (!isSocketConnected()) { - throw new SocketException("ZeroTierSocket is not connected"); - } - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - if (isInputStreamShutdown()) { - throw new SocketException("ZeroTierSocket input is shutdown"); - } - return getImpl().getInputStream(); - } - - /* - * Returns whether SO_KEEPALIVE is enabled - */ - public boolean getKeepAlive() - throws SocketException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - return ((Boolean) getImpl().getOption(ZeroTier.SO_KEEPALIVE)).booleanValue(); - } - - /* - * Returns the local address to which the socket is bound - */ - public InetAddress getLocalAddress() - { - System.err.println("getLocalAddress: ZeroTierSocket does not currently support this feature"); - /* - // This is for backward compatibility - if (!isSocketBound()) { - return InetAddress.anyLocalAddress(); - } - InetAddress inAddr = null; - try { - inAddr = (InetAddress) getImpl().getOption(ZeroTier.SO_BINDADDR); - if (inAddr.isAnyLocalAddress()) { - inAddr = InetAddress.anyLocalAddress(); - } - } - catch (Exception ex) { - // "0.0.0.0" - inAddr = InetAddress.anyLocalAddress(); - } - return inAddr; - */ - return null; - } - - /* - * Return the local port to which the socket is bound - */ - public int getLocalPort() - { - if (!isSocketBound()) { - return -1; - } - try { - return getImpl().getLocalPort(); - } - catch(SocketException ex) { - // Unreachable - } - return -1; - } - - /* - * Returns the address of the endpoint that the socket is bound to. - */ - public SocketAddress getLocalSocketAddress() - { - if (!isSocketBound()) { - return null; - } - return new InetSocketAddress(getLocalAddress(), getLocalPort()); - } - - /* - * Returns whether SO_OOBINLINE is enabled. - */ - public boolean getOOBInline​() - throws SocketException - { - System.err.println("getOOBInline​: ZeroTierSocket does not currently support this feature"); - return false; - } - - /* - * Returns the output stream. - */ - public ZeroTierOutputStream getOutputStream() - throws IOException - { - if (!isSocketConnected()) { - throw new SocketException("ZeroTierSocket is not connected"); - } - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - if (isOutputStreamShutdown()) { - throw new SocketException("ZeroTierSocket output is shutdown"); - } - return getImpl().getOutputStream(); - } - - /* - * Return the remote port to which the socket is connected - */ - public int getPort() - { - if (!isSocketConnected()) { - return 0; - } - try { - return getImpl().getPort(); - } - catch (SocketException ex) { - // Not reachable - } - return -1; - } - - /* - * Returns SO_RCVBUF - */ - public synchronized int getReceiveBufferSize() - throws SocketException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - Object opt = getImpl().getOption(ZeroTier.SO_RCVBUF); - int sz = 0; - if (opt instanceof Integer) { - sz = ((Integer)opt).intValue(); - } - return sz; - } - - /* - * Returns the remote address to which this socket is connected - */ - public SocketAddress getRemoteSocketAddress() - { - if (!isSocketConnected()) { - return null; - } - return new InetSocketAddress(getInetAddress(), getPort()); - } - - /* - * Checks whether SO_REUSEADDR is enabled. - */ - public boolean getReuseAddress() - throws SocketException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - return ((Boolean)(getImpl().getOption(ZeroTier.SO_REUSEADDR))).booleanValue(); - } - - /* - * Returns SO_SNDBUF. - */ - public synchronized int getSendBufferSize() - throws SocketException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - int sz = 0; - Object opt = getImpl().getOption(ZeroTier.SO_SNDBUF); - if (opt instanceof Integer) { - sz = ((Integer)opt).intValue(); - } - return sz; - } - - /* - * Returns SO_LINGER. - */ - public int getSoLinger() - throws SocketException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - Object opt = getImpl().getOption(ZeroTier.SO_LINGER); - if (opt instanceof Integer) { - return ((Integer)opt).intValue(); - } - return -1; - } - - /* - * Returns SO_TIMEOUT. - */ - public synchronized int getSoTimeout() - throws SocketException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - Object opt = getImpl().getOption(ZeroTier.SO_RCVTIMEO); - if (opt instanceof Integer) { - return ((Integer)opt).intValue(); - } - else { - return 0; - } - } - - /* - * Checks whether TCP_NODELAY is enabled. - */ - public boolean getTcpNoDelay() - throws SocketException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - return ((Boolean)getImpl().getOption(ZeroTier.TCP_NODELAY)).booleanValue(); - } - - /* - * Gets traffic class or type-of-service in the IP header for packets sent from this Socket - */ - public int getTrafficClass​() - throws SocketException - { - System.err.println("getTrafficClass​: ZeroTierSocket does not currently support this feature"); - return 0; - } - - /* - * Returns whether or not the socket is bound to a local interface. - */ - public boolean isSocketBound​() - { - return bound; - } - - /* - * Returns whether or not the socket has been closed. - */ - public boolean isSocketClosed​() - { - return closed; - } - - /* - * Returns whether or not the socket is connected to a remote host. - */ - public boolean isSocketConnected​() - { - return connected; - } - - /* - * Returns whether the input aspect of the socket has been disabled. - */ - public boolean isInputStreamShutdown​() - { - return inputShutdown; - } - - /* - * Returns whether the output aspect of the socket has been disabled. - */ - public boolean isOutputStreamShutdown​() - { - return outputShutdown; - } - - /* - * Send a byte of urgent data on the socket. - */ - public void sendUrgentData​(int data) - throws IOException - { - System.err.println("sendUrgentData​: ZeroTierSocket does not currently support this feature"); - } - - /* - * Enable or disable SO_KEEPALIVE. - */ - public void setKeepAlive(boolean on) - throws SocketException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - getImpl().setOption(ZeroTier.SO_KEEPALIVE, Boolean.valueOf(on)); - } - - /* - * Enable or disable SO_OOBINLINE. - */ - public void setOOBInline​(boolean on) - throws SocketException - { - System.err.println("setOOBInline​: ZeroTierSocket does not currently support this feature"); - } - - /* - * Set performance preferences. - */ - public void setPerformancePreferences​(int connectionTime, int latency, int bandwidth) - { - System.err.println("setPerformancePreferences​: ZeroTierSocket does not currently support this feature"); - } - - /* - * Set SO_RCVBUF. - */ - public synchronized void setReceiveBufferSize(int size) - throws SocketException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - if (size <= 0) { - throw new IllegalArgumentException("invalid receive buffer size argument"); - } - getImpl().setOption(ZeroTier.SO_RCVBUF, new Integer(size)); - } - - /* - * Enable or disable SO_REUSEADDR. - */ - public void setReuseAddress(boolean on) - throws SocketException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - getImpl().setOption(ZeroTier.SO_REUSEADDR, Boolean.valueOf(on)); - } - - /* - * Set SO_SNDBUF. - */ - public synchronized void setSendBufferSize(int size) - throws SocketException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - if (size < 0) { - throw new IllegalArgumentException("size argument cannot be negative"); - } - getImpl().setOption(ZeroTier.SO_SNDBUF, new Integer(size)); - } - - /* - * Set Socket implementation factory for all clients. - */ - public static void setSocketImplFactory​(ZeroTierSocketImplFactory fact) - throws IOException - { - if (factory != null) { - throw new SocketException("ZeroTierSocket factory is already defined"); - } - factory = fact; - } - - /* - * Enable or disable SO_LINGER time (seconds). - */ - public void setSoLinger(boolean on, int linger) - throws SocketException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - if (!on) { - getImpl().setOption(ZeroTier.SO_LINGER, new Boolean(on)); - } - else { - if (linger < 0) { - throw new IllegalArgumentException("linger argument is invalid"); - } - if (linger > 0xFFFF) { - linger = 0xFFFF; - } - getImpl().setOption(ZeroTier.SO_LINGER, new Integer(linger)); - } - } - - /* - * Enable or disable SO_TIMEOUT with the specified timeout, in milliseconds. - */ - public void setSoTimeout​(int timeout) - throws SocketException - { - if (timeout < 0) { - throw new IllegalArgumentException("timeout argument cannot be negative"); - } - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - getImpl().setOption(ZeroTier.SO_RCVTIMEO, new Integer(timeout)); - } - - /* - * Enable or disable TCP_NODELAY (Nagle's algorithm). - */ - public void setTcpNoDelay(boolean on) - throws SocketException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - getImpl().setOption(ZeroTier.TCP_NODELAY, Boolean.valueOf(on)); - } - - /* - * Sets traffic class or ToS. - */ - public void setTrafficClass​(int tc) - throws SocketException - { - System.err.println("setTrafficClass​: ZeroTierSocket does not currently support this feature"); - } - - /* - * Disable the input stream for this socket. - */ - public void shutdownInput() - throws IOException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - if (isInputStreamShutdown()) { - throw new SocketException("ZeroTierSocket input is already shutdown"); - } - if (!isSocketConnected()) { - throw new SocketException("ZeroTierSocket is not connected"); - } - getImpl().shutdownInput(); - inputShutdown = true; - } - - /* - * Disable the output stream for this socket. - */ - public void shutdownOutput() - throws IOException - { - if (isSocketClosed()) { - throw new SocketException("ZeroTierSocket is closed"); - } - if (isOutputStreamShutdown()) { - throw new SocketException("ZeroTierSocket output is already shutdown"); - } - if (!isSocketConnected()) { - throw new SocketException("ZeroTierSocket is not connected"); - } - getImpl().shutdownOutput(); - outputShutdown = true; - } - - /* - * Gets the underlying implementation's file descriptor. - */ - /* - public FileDescriptor getFileDescriptor() - { - return impl.getFileDescriptor(); - } - */ -} \ No newline at end of file diff --git a/src/java/ZeroTierSocketAddress.java b/src/java/ZeroTierSocketAddress.java deleted file mode 100644 index e773c09..0000000 --- a/src/java/ZeroTierSocketAddress.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.zerotier.libzt; - -import com.zerotier.libzt.ZeroTier; - -import java.net.InetAddress; - -// Designed to transport address information across the JNI boundary -public class ZeroTierSocketAddress -{ - public byte[] _ip6 = new byte[16]; - public byte[] _ip4 = new byte[4]; - - public int _family; - public int _port; // Also reused for netmask or prefix - - public ZeroTierSocketAddress() {} - - public ZeroTierSocketAddress(String ipStr, int port) - { - if(ipStr.contains(":")) { - _family = ZeroTier.AF_INET6; - try { - InetAddress ip = InetAddress.getByName(ipStr); - _ip6 = ip.getAddress(); - } - catch (Exception e) { } - } - else if(ipStr.contains(".")) { - _family = ZeroTier.AF_INET; - try { - InetAddress ip = InetAddress.getByName(ipStr); - _ip4 = ip.getAddress(); - } - catch (Exception e) { } - } - _port = port; - } - - public int getPort() { return _port; } - public int getNetmask() { return _port; } - public int getPrefix() { return _port; } - - public String ipString() - { - if (_family == ZeroTier.AF_INET) { - try { - InetAddress inet = InetAddress.getByAddress(_ip4); - return "" + inet.getHostAddress(); - } catch (Exception e) { - System.out.println(e); - } - } - if (_family == ZeroTier.AF_INET6) { - try { - InetAddress inet = InetAddress.getByAddress(_ip6); - return "" + inet.getHostAddress(); - } catch (Exception e) { - System.out.println(e); - } - } - return ""; - } - - public String toString() { return ipString() + ":" + _port; } - public String toCIDR() { return ipString() + "/" + _port; } -} diff --git a/src/java/ZeroTierSocketFactory.java b/src/java/ZeroTierSocketFactory.java deleted file mode 100644 index 0e79a65..0000000 --- a/src/java/ZeroTierSocketFactory.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.zerotier.libzt; - -import com.zerotier.libzt.ZeroTier; -import com.zerotier.libzt.ZeroTierSocket; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.net.UnknownHostException; -import javax.net.SocketFactory; -import java.util.Objects; - -public class ZeroTierSocketFactory extends SocketFactory -{ - public ZeroTierSocketFactory() { } - - public static SocketFactory getDefault() - { - return null; - } - - public Socket createSocket() - throws IOException, UnknownHostException - { - return new ZeroTierSocket(); - } - - public Socket createSocket(String host, int port) - throws IOException, UnknownHostException - { - return new ZeroTierSocket(host, port); - } - - public Socket createSocket(String host, int port, InetAddress localHost, int localPort) - throws IOException, UnknownHostException - { - return new ZeroTierSocket(host, port, localHost, localPort); - } - - public Socket createSocket(InetAddress host, int port) - throws IOException - { - return new ZeroTierSocket(host, port); - } - - public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) - throws IOException - { - return new ZeroTierSocket(address, port, localAddress, localPort); - } -} diff --git a/src/java/ZeroTierSocketImpl.java b/src/java/ZeroTierSocketImpl.java deleted file mode 100644 index cbb21d7..0000000 --- a/src/java/ZeroTierSocketImpl.java +++ /dev/null @@ -1,771 +0,0 @@ -package com.zerotier.libzt; - -import com.zerotier.libzt.ZeroTier; -import com.zerotier.libzt.ZeroTierSocketAddress; -import com.zerotier.libzt.ZeroTierInputStream; -import com.zerotier.libzt.ZeroTierOutputStream; -import com.zerotier.libzt.ZeroTierSocketOptionValue; - -import java.io.*; -import java.net.*; -import java.util.Objects; -import java.lang.Object; -import java.net.SocketImpl; -import java.net.InetSocketAddress; -import java.util.Set; -import java.lang.Boolean; - -import com.zerotier.libzt.ZeroTier; - -public class ZeroTierSocketImpl extends SocketImpl -{ - private int defaultProtocol = 0; - - /* - * File descriptor from lower native layer - */ - private int zfd = -1; - private int zfd4 = -1; - private int zfd6 = -1; - - /* - * Input and Output streams - */ - private ZeroTierInputStream in = new ZeroTierInputStream(); - private ZeroTierOutputStream out = new ZeroTierOutputStream(); - - Socket socket = null; - ServerSocket serverSocket = null; - - /* - * The remote address the socket is connected to - */ - protected InetAddress address; - - /* - * Sets the underlying file descriptor valud for the SocketImpl as well as the Input/OutputStream - */ - private void setNativeFileDescriptor(int fd) - { - zfd = fd; - in.zfd = fd; - out.zfd = fd; - } - - /* - * Various socket options that are cached from calls to setOption() before - * the socket exists. - */ - private int _so_rcvtimeo; - private boolean _so_keepalive; - private int _so_sndbuf; - private int _so_rcvbuf; - private boolean _so_reuseaddr; - private int _so_linger; - private int _so_tos; - private boolean _so_nodelay; - - private void setCachedSocketOptions(int fd) - { - if (fd < 0) { - return; - } - - // If we previously received a setSoTimeout() call but were unable to process it due - // to the fact that the underlying socket didn't even exist yet, do so now. - int err = 0; - ZeroTierSocketOptionValue optval = new ZeroTierSocketOptionValue(); - - try { - // SO_TIMEOUT - if (_so_rcvtimeo > 0) { - optval.isInteger = true; - optval.isBoolean = false; - optval.integerValue = ((Integer)_so_rcvtimeo).intValue(); - if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.SO_RCVTIMEO, optval)) < 0) { - throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached SO_RCVTIMEO"); - } - } - // SO_KEEPALIVE - if (_so_keepalive == true) { - optval.isInteger = false; - optval.isBoolean = true; - optval.booleanValue = ((Boolean)_so_keepalive).booleanValue() ? true : false; - if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.SO_KEEPALIVE, optval)) < 0) { - throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached SO_KEEPALIVE"); - } - } - // SO_SNDBUF - if (_so_sndbuf > 0) { - optval.isInteger = true; - optval.isBoolean = false; - optval.integerValue = ((Integer)_so_sndbuf).intValue(); - if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.SO_SNDBUF, optval)) < 0) { - throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached SO_SNDBUF"); - } - } - // SO_RCVBUF - if (_so_rcvbuf > 0) { - optval.isInteger = true; - optval.isBoolean = false; - optval.integerValue = ((Integer)_so_rcvbuf).intValue(); - if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.SO_RCVBUF, optval)) < 0) { - throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached SO_RCVBUF"); - } - } - // SO_REUSEADDR - if (_so_reuseaddr == true) { - optval.isInteger = false; - optval.isBoolean = true; - optval.booleanValue = ((Boolean)_so_reuseaddr).booleanValue() ? true : false; - if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.SO_REUSEADDR, optval)) < 0) { - throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached SO_REUSEADDR"); - } - } - // SO_LINGER - if (_so_linger > 0) { - optval.isInteger = true; - optval.isBoolean = false; - optval.integerValue = ((Integer)_so_linger).intValue(); - if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.SO_LINGER, optval)) < 0) { - throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached SO_LINGER"); - } - } - // IP_TOS - if (_so_tos > 0) { - optval.isInteger = true; - optval.isBoolean = false; - optval.integerValue = ((Integer)_so_tos).intValue(); - if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.IP_TOS, optval)) < 0) { - throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached IP_TOS"); - } - } - // TCP_NODELAY - if (_so_nodelay == true) { - optval.isInteger = false; - optval.isBoolean = true; - optval.booleanValue = ((Boolean)_so_nodelay).booleanValue() ? true : false; - if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.TCP_NODELAY, optval)) < 0) { - throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached TCP_NODELAY"); - } - } - } - catch (Exception e) { - System.err.println(e); - } - } - - /* - * Constructor which creates a new ZeroTierSocketImpl - */ - public ZeroTierSocketImpl() - { - if ((zfd > -1) | (zfd4 > -1) | (zfd6 > -1)) { return; } - try { - create(true); - } catch (Exception x) { - System.err.println("error creating ZeroTierSocketImpl instance: " + x); - } - in.zfd = zfd; - out.zfd = zfd; - } - - /* - * Constructor to be called when an underlying ZeroTier socket already exists (does not create a new ZeroTierSocketImpl) - */ - public ZeroTierSocketImpl(int fd) { - setNativeFileDescriptor(fd); - } - - /* - * Creates a new ZeroTier socket in the native layer - */ - protected void create(boolean stream) - throws IOException - { - /* - * The native-layer socket is only created once a connect/bind call is made, this is due to the fact - * that beforehand we have no way to determine whether we should create an AF_INET or AF_INET6 socket, - * as a result, this method intentionally does nothing. - */ - } - - /* - * Creates the underlying libzt socket. - * - * This does the real work that can't be done in the constructor. This is because the socket address type - * isn't known until a connect() or bind() request is given. Additionally we cache the value provided by any - * setSoTimeout() calls and implement it immediately after creation. - */ - private void createAppropriateSocketImpl(InetAddress addr) - throws IOException - { - if ((zfd > -1) | (zfd4 > -1) | (zfd6 > -1)) { - return; // Socket already created - } - if(addr instanceof Inet4Address) { - if ((zfd4 = ZeroTier.socket(ZeroTier.AF_INET, ZeroTier.SOCK_STREAM, defaultProtocol)) < 0) { - throw new IOException("socket(), errno="+zfd4+", see: libzt/ext/lwip/src/include/errno.h"); - } - setCachedSocketOptions(zfd4); - } - /* - * Since Java creates sockets capable of handling IPV4 and IPV6, we must simulate this. We do this by - * creating two sockets (one of each type) - */ - if(addr instanceof Inet6Address) { - if ((zfd4 = ZeroTier.socket(ZeroTier.AF_INET, ZeroTier.SOCK_STREAM, defaultProtocol)) < 0) { - throw new IOException("socket(), errno="+zfd4+", see: libzt/ext/lwip/src/include/errno.h"); - } - if ((zfd6 = ZeroTier.socket(ZeroTier.AF_INET6, ZeroTier.SOCK_STREAM, defaultProtocol)) < 0) { - throw new IOException("socket(), errno="+zfd6+", see: libzt/ext/lwip/src/include/errno.h"); - } - setCachedSocketOptions(zfd4); - setCachedSocketOptions(zfd6); - } - } - - /* - * Return the remote address the socket is connected to - */ - protected InetAddress getInetAddress() - { - return address; - } - - /* - * Connects the socket to a remote address - */ - protected void connect(String host, int port) - throws IOException - { - // TODO: Refactor and consolidate the connect() logic for all three methods - createAppropriateSocketImpl(InetAddress.getByName(host)); - if ((zfd4 < 0) & (zfd6 < 0)) { - throw new IOException("invalid fd"); - } - int err; - InetAddress address = InetAddress.getByName(host); - ZeroTierSocketAddress zt_addr = new ZeroTierSocketAddress(host, port); - if (address instanceof Inet4Address) { - if ((err = ZeroTier.connect(zfd4, zt_addr)) < 0) { - throw new IOException("connect(), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); - } - setNativeFileDescriptor(zfd4); - } - if (address instanceof Inet6Address) { - if ((err = ZeroTier.connect(zfd6, zt_addr)) < 0) { - throw new IOException("connect(), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); - } - setNativeFileDescriptor(zfd6); - } - super.port = port; - } - - /* - * Connects the socket to a remote address - */ - protected void connect(InetAddress address, int port) - throws IOException - { - // TODO: Refactor and consolidate the connect() logic for all three methods - createAppropriateSocketImpl(address); - if ((zfd4 < 0) & (zfd6 < 0)) { - throw new IOException("invalid fd"); - } - int err; - ZeroTierSocketAddress zt_addr = new ZeroTierSocketAddress(address.getHostAddress(), port); - if (address instanceof Inet4Address) { - if ((err = ZeroTier.connect(zfd4, zt_addr)) < 0) { - throw new IOException("connect(), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); - } - setNativeFileDescriptor(zfd4); - } - if (address instanceof Inet6Address) { - if ((err = ZeroTier.connect(zfd6, zt_addr)) < 0) { - throw new IOException("connect(), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); - } - setNativeFileDescriptor(zfd6); - } - super.port = port; - } - - /* - * Connects the socket to a remote address - */ - protected void connect(SocketAddress address, int timeout) - throws IOException - { - // TODO: Refactor and consolidate the connect() logic for all three methods - //System.out.println("host="+((InetSocketAddress)address).getHostString()+", port="+((InetSocketAddress)address).getPort() + ", timeout="+timeout); - createAppropriateSocketImpl(((InetSocketAddress)address).getAddress()); - if ((zfd4 < 0) & (zfd6 < 0)) { - throw new IOException("invalid fd"); - } - ZeroTierSocketAddress zt_addr = null; - int err; - int port = ((InetSocketAddress)address).getPort(); - if (((InetSocketAddress)address).getAddress() instanceof Inet4Address) { - zt_addr = new ZeroTierSocketAddress(((InetSocketAddress)address).getHostString(), ((InetSocketAddress)address).getPort()); - if ((err = ZeroTier.connect(zfd4, zt_addr)) < 0) { - throw new IOException("connect("+zfd4+"), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); - } - setNativeFileDescriptor(zfd4); - } - if (((InetSocketAddress)address).getAddress() instanceof Inet6Address) { - zt_addr = new ZeroTierSocketAddress(((InetSocketAddress)address).getHostString(), ((InetSocketAddress)address).getPort()); - if ((err = ZeroTier.connect(zfd6, zt_addr)) < 0) { - throw new IOException("connect("+zfd6+"), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); - } - setNativeFileDescriptor(zfd6); - } - super.port = port; - } - - /* - * Binds the socket to a local address. - * - * If this gets a bind() request on [::] it will create a both an IPv4 and an IPv6 - * socket. This is because we might receive a subsequent listen() and accept() request - * and want to accept an IPv6 connection. (or) we may get a connect() request with - * an IPv4 address. In the latter case we must abandon the IPv6 socket and use the IPv4 - * socket exclusively. - */ - protected void bind(InetAddress host, int port) - throws IOException - { - createAppropriateSocketImpl(host); - /* - After this point we may have either a) created a single IPv4 socket, or b) created - an IPv4 and IPv6 socket in anticipation of either verion being used - */ - //System.out.println("host="+host.toString()+", port="+port); - int err; - if ((zfd < 0) & (zfd4 < 0) & (zfd6 < 0)) { - throw new IOException("invalid fd"); - } - ZeroTierSocketAddress zt_addr = new ZeroTierSocketAddress(host.getHostAddress(), port); - - if (zfd6 > -1) { - // Since an IPv6 socket and accept IPv4 connections we will only bind to this address - if ((err = ZeroTier.bind(zfd6, zt_addr)) < 0) { - throw new IOException("bind("+zfd6+"), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); - } - super.localport = port; - return; - } - if (zfd4 > -1) { - // Otherwise, just bind to the regular IPv4 address - if ((err = ZeroTier.bind(zfd4, zt_addr)) < 0) { - throw new IOException("bind("+zfd4+"), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); - } - super.localport = port; - return; - } - - } - - /* - * Puts the socket into a listening state. - * - * We listen on the IPv6 socket since it can listen for IPv4 connections - */ - protected void listen(int backlog) - throws IOException - { - int err; - if ((zfd6 < 0) | (backlog < 0)) { - throw new IOException("invalid fd and/or backlog"); - } - if ((err = ZeroTier.listen(zfd6, backlog)) < 0) { - throw new IOException("listen("+zfd6+"), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); - } - } - - /* - * Accepts an incoming connection. - * - * We accept on the IPv6 socket since it can accept IPv4 connections - */ - protected void accept(SocketImpl si) - throws IOException - { - if (zfd6 < 0) { - throw new IOException("invalid fd"); - } - int accetpedFd = -1; - ZeroTierSocketAddress addr = new ZeroTierSocketAddress(); - if ((accetpedFd = ZeroTier.accept(zfd6, addr)) < 0) { - throw new IOException("accept("+zfd6+"), errno="+accetpedFd+", see: libzt/ext/lwip/src/include/errno.h"); - } - // Give the new socket fd from the native layer to the new unconnected ZeroTierSocketImpl - ((ZeroTierSocketImpl)si).setFileDescriptor(accetpedFd); - } - - /* - * Returns the input stream for this socket - */ - protected ZeroTierInputStream getInputStream() - throws IOException - { - if (in == null) { - throw new IOException(); - } - return in; - } - - /* - * Returns the output stream for this socket - */ - protected ZeroTierOutputStream getOutputStream() - throws IOException - { - if (out == null) { - throw new IOException(); - } - return out; - } - - /* - * Returns the remote port to which this socket is connected - */ - protected int getPort() - { - return super.port; - } - - /* - * Returns the local port to which this socket is bound - */ - protected int getLocalPort() - { - return super.localport; - } - - /* - * Returns whether this socket implementation supports urgent data (hint: it doesn't) - */ - protected boolean supportsUrgentData() - { - return false; - } - - /* - * - */ - void setSocket(ZeroTierSocket soc) - { - this.socket = soc; - } - - /* - * - */ - Socket getSocket() - { - return socket; - } - - /* - * - */ - /* - void setServerSocket(ZeroTierServerSocket soc) - { - this.serverSocket = soc; - } - */ - - /* - * - */ - ServerSocket getServerSocket() - { - return serverSocket; - } - - /* - * Return the number of bytes that can be read from the socket without blocking - */ - protected int available() - throws IOException - { - // TODO - return 0; - } - - /* - * Closes the socket - */ - protected void close() - throws IOException - { - if (zfd > -1) { - ZeroTier.close(zfd); - } - if (zfd4 > -1) { - ZeroTier.close(zfd4); - } - if (zfd6 > -1) { - ZeroTier.close(zfd6); - } - } - - /* - * Send one byte of urgent data on the socket - */ - protected void sendUrgentData(int data) - throws IOException - { - System.err.println("sendUrgentData: ZeroTierSocketImpl does not currently support this feature"); - } - - /* - * Gets some specified socket option - */ - public Object getOption(int optID) - throws SocketException - { - // Call native layer - ZeroTierSocketOptionValue optval = new ZeroTierSocketOptionValue(); - int option = -1; - int level = -1; - - if (zfd < 0) { // If we haven't committed to a socket version yet, cache the value - if (optID == SocketOptions.SO_TIMEOUT || optID == ZeroTier.SO_RCVTIMEO) { - return Integer.valueOf(_so_rcvtimeo); - } - if (optID == SocketOptions.SO_KEEPALIVE || optID == ZeroTier.SO_KEEPALIVE) { - return Boolean.valueOf(_so_keepalive); - } - if (optID == SocketOptions.SO_SNDBUF || optID == ZeroTier.SO_SNDBUF) { - return Integer.valueOf(_so_sndbuf); - } - if (optID == SocketOptions.SO_RCVBUF || optID == ZeroTier.SO_RCVBUF) { - return Integer.valueOf(_so_rcvbuf); - } - if (optID == SocketOptions.SO_REUSEADDR || optID == ZeroTier.SO_REUSEADDR) { - return Boolean.valueOf(_so_reuseaddr); - } - if (optID == SocketOptions.SO_LINGER || optID == ZeroTier.SO_LINGER) { - return Integer.valueOf(_so_linger); - } - if (optID == SocketOptions.IP_TOS || optID == ZeroTier.IP_TOS) { - return Integer.valueOf(_so_tos); - } - if (optID == SocketOptions.TCP_NODELAY || optID == ZeroTier.TCP_NODELAY) { - return Boolean.valueOf(_so_nodelay); - } - } - else { - if (optID == SocketOptions.SO_TIMEOUT || optID == ZeroTier.SO_RCVTIMEO) { - option = ZeroTier.SO_RCVTIMEO; - level = ZeroTier.SOL_SOCKET; - } - if (optID == SocketOptions.SO_KEEPALIVE || optID == ZeroTier.SO_KEEPALIVE) { - option = ZeroTier.SO_KEEPALIVE; - level = ZeroTier.SOL_SOCKET; - } - if (optID == SocketOptions.SO_SNDBUF || optID == ZeroTier.SO_SNDBUF) { - option = ZeroTier.SO_SNDBUF; - level = ZeroTier.SOL_SOCKET; - } - if (optID == SocketOptions.SO_RCVBUF || optID == ZeroTier.SO_RCVBUF) { - option = ZeroTier.SO_RCVBUF; - level = ZeroTier.SOL_SOCKET; - } - if (optID == SocketOptions.SO_REUSEADDR || optID == ZeroTier.SO_REUSEADDR) { - option = ZeroTier.SO_REUSEADDR; - level = ZeroTier.SOL_SOCKET; - } - if (optID == SocketOptions.SO_LINGER || optID == ZeroTier.SO_LINGER) { - option = ZeroTier.SO_LINGER; - level = ZeroTier.SOL_SOCKET; - } - // IP - if (optID == SocketOptions.IP_TOS || optID == ZeroTier.IP_TOS) { - option = ZeroTier.IP_TOS; - level = ZeroTier.IPPROTO_IP; - } - // TCP - if (optID == SocketOptions.TCP_NODELAY || optID == ZeroTier.TCP_NODELAY) { - option = ZeroTier.TCP_NODELAY; - level = ZeroTier.IPPROTO_TCP; - } - ZeroTier.getsockopt(zfd, level, option, optval); - // Convert native layer's response into Java object of some sort - if (optval.isBoolean) { - return Boolean.valueOf(optval.booleanValue); - } - if (optval.isInteger) { - return Integer.valueOf(optval.integerValue); - } - } - return null; - } - - /* - * Sets a socket option to a specified value. This method should be able to handle SocketOptions values - * as well as native ZeroTier.* options - */ - public void setOption(int optID, Object value) - throws SocketException - { - if (value == null) { - throw new UnsupportedOperationException(); - } - - int option = -1; - int level = -1; - - ZeroTierSocketOptionValue optval = new ZeroTierSocketOptionValue(); - - if (zfd < 0) { // If we haven't committed to a socket version yet, cache the value - if (optID == SocketOptions.SO_TIMEOUT || optID == ZeroTier.SO_RCVTIMEO) { - _so_rcvtimeo = ((Integer)value).intValue(); return; - } - if (optID == SocketOptions.SO_KEEPALIVE || optID == ZeroTier.SO_KEEPALIVE) { - _so_keepalive = ((Boolean)value).booleanValue() ? true : false; return; - } - if (optID == SocketOptions.SO_SNDBUF || optID == ZeroTier.SO_SNDBUF) { - _so_sndbuf = ((Integer)value).intValue(); return; - } - if (optID == SocketOptions.SO_RCVBUF || optID == ZeroTier.SO_RCVBUF) { - _so_rcvbuf = ((Integer)value).intValue(); return; - } - if (optID == SocketOptions.SO_REUSEADDR || optID == ZeroTier.SO_REUSEADDR) { - _so_reuseaddr = ((Boolean)value).booleanValue() ? true : false; return; - } - if (optID == SocketOptions.SO_LINGER || optID == ZeroTier.SO_LINGER) { - _so_linger = ((Integer)value).intValue(); return; - } - if (optID == SocketOptions.IP_TOS || optID == ZeroTier.IP_TOS) { - _so_tos = ((Integer)value).intValue(); return; - } - if (optID == SocketOptions.TCP_NODELAY || optID == ZeroTier.TCP_NODELAY) { - _so_nodelay = ((Boolean)value).booleanValue() ? true : false; return; - } - } - else { - // SOL - if (optID == SocketOptions.SO_TIMEOUT || optID == ZeroTier.SO_RCVTIMEO) { - option = ZeroTier.SO_RCVTIMEO; - level = ZeroTier.SOL_SOCKET; - if (value instanceof Integer) { - optval.isInteger = true; - optval.integerValue = ((Integer)value).intValue(); - } - } - - if (optID == SocketOptions.SO_KEEPALIVE || optID == ZeroTier.SO_KEEPALIVE) { - option = ZeroTier.SO_KEEPALIVE; - level = ZeroTier.SOL_SOCKET; - if (value instanceof Integer) { - optval.isBoolean = true; - optval.booleanValue = ((Boolean)value).booleanValue() ? true : false; - } - } - if (optID == SocketOptions.SO_SNDBUF || optID == ZeroTier.SO_SNDBUF) { - option = ZeroTier.SO_SNDBUF; - level = ZeroTier.SOL_SOCKET; - if (value instanceof Integer) { - optval.isInteger = true; - optval.integerValue = ((Integer)value).intValue(); - } - } - if (optID == SocketOptions.SO_RCVBUF || optID == ZeroTier.SO_RCVBUF) { - option = ZeroTier.SO_RCVBUF; - level = ZeroTier.SOL_SOCKET; - if (value instanceof Integer) { - optval.isInteger = true; - optval.integerValue = ((Integer)value).intValue(); - } - } - if (optID == SocketOptions.SO_REUSEADDR || optID == ZeroTier.SO_REUSEADDR) { - option = ZeroTier.SO_REUSEADDR; - level = ZeroTier.SOL_SOCKET; - if (value instanceof Integer) { - optval.isBoolean = true; - optval.booleanValue = ((Boolean)value).booleanValue() ? true : false; - } - } - if (optID == SocketOptions.SO_LINGER || optID == ZeroTier.SO_LINGER) { - option = ZeroTier.SO_LINGER; - level = ZeroTier.SOL_SOCKET; - if (value instanceof Integer) { - optval.isInteger = true; - optval.integerValue = ((Integer)value).intValue(); - } - } - // IP - if (optID == SocketOptions.IP_TOS || optID == ZeroTier.IP_TOS) { - option = ZeroTier.IP_TOS; - level = ZeroTier.IPPROTO_IP; - if (value instanceof Integer) { - optval.isInteger = true; - optval.integerValue = ((Integer)value).intValue(); - } - } - // TCP - if (optID == SocketOptions.TCP_NODELAY || optID == ZeroTier.TCP_NODELAY) { - option = ZeroTier.TCP_NODELAY; - level = ZeroTier.IPPROTO_TCP; - if (value instanceof Integer) { - optval.isBoolean = true; - optval.booleanValue = ((Boolean)value).booleanValue() ? true : false; - } - } - - if (option < 0) { // No option was properly set - //throw new UnsupportedOperationException(); - } - ZeroTier.setsockopt(zfd, level, option, optval); - } - } - - /* - * Disables the input aspect of the socket - */ - public void shutdownInput() - { - ZeroTier.shutdown(zfd, ZeroTier.SHUT_RD); - // Alternatively: getInputStream().close(); - } - - /* - * Disables the output aspect of the socket - */ - public void shutdownOutput() - { - ZeroTier.shutdown(zfd, ZeroTier.SHUT_WR); - // Alternatively: getOutputStream().close(); - } - - /* - * Sets the file descriptor - */ - public void setFileDescriptor(int fd) - { - zfd = fd; - } - - /* - * Resets the socket - */ - void reset() - throws IOException - { - localport = 0; - address = null; - port = 0; - } -/* - public FileDescriptor getFileDescriptor() - { - // TODO: Should probably remove this for production - System.out.println("getFileDescriptor(), zfd="+zfd); - ParcelFileDescriptor pfd = ParcelFileDescriptor.adoptFd(zfd); - return pfd.getFileDescriptor(); - } -*/ -} \ No newline at end of file diff --git a/src/java/ZeroTierSocketImplFactory.java b/src/java/ZeroTierSocketImplFactory.java deleted file mode 100644 index f04bf95..0000000 --- a/src/java/ZeroTierSocketImplFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.zerotier.libzt; - -import com.zerotier.libzt.ZeroTier; -import com.zerotier.libzt.ZeroTierSocketImpl; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.net.UnknownHostException; -import javax.net.SocketFactory; -import java.net.SocketImplFactory; -import java.util.Objects; - - -public class ZeroTierSocketImplFactory implements SocketImplFactory -{ - /* - * Does nothing - */ - public ZeroTierSocketImplFactory() { } - - /* - * Produces a ZeroTierSocketImpl - */ - public ZeroTierSocketImpl createSocketImpl() - { - return new ZeroTierSocketImpl(); - } -} diff --git a/src/java/ZeroTierSocketOptionValue.java b/src/java/ZeroTierSocketOptionValue.java deleted file mode 100644 index 15bbfef..0000000 --- a/src/java/ZeroTierSocketOptionValue.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.zerotier.libzt; - -public class ZeroTierSocketOptionValue -{ - public boolean isBoolean = false; - public boolean booleanValue; - - public boolean isInteger = false; - public int integerValue; - - public boolean isTimeval = false; - public int tv_sec; // seconds - public int tv_usec; // microseconds -} \ No newline at end of file From 4e578276376b045bd31ac57112a7e80c65fe7598 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Wed, 10 Feb 2021 18:12:01 -0800 Subject: [PATCH 02/25] Update Android build scripts. Remove unnecessary project files --- Makefile | 7 +- dist.sh | 75 ++++---- .../gradle/wrapper/gradle-wrapper.properties | 2 +- examples/android/ExampleAndroidApp/gradlew | 0 .../android/ExampleAndroidApp/gradlew.bat | 0 examples/python/zt.i | 30 +++ examples/rust/binding-example/Cargo.lock | 5 + examples/rust/binding-example/Cargo.toml | 10 + examples/rust/binding-example/build.rs | 4 + examples/rust/binding-example/src/main.rs | 10 + examples/rust/program.c | 6 + examples/rust/program.h | 1 + examples/rust/program.rs | 5 + {ports => pkg}/android/.gitignore | 0 {ports => pkg}/android/.project | 0 .../org.eclipse.buildship.core.prefs | 0 {ports => pkg}/android/app/.gitignore | 0 {ports => pkg}/android/app/build.gradle | 0 {ports => pkg}/android/app/proguard-rules.pro | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../com/example/zerotier/MainActivity.java | 0 .../src/main/java/com/zerotier/libzt/README | 1 + {ports => pkg}/android/build.gradle | 2 +- {ports => pkg}/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 1 - gradlew => pkg/android/gradlew | 0 gradlew.bat => pkg/android/gradlew.bat | 0 {ports => pkg}/android/settings.gradle | 0 .../zerotier/ExampleInstrumentedTest.java | 26 --- .../src/main/java/com/zerotier/libzt/README | 1 - .../drawable-v24/ic_launcher_foreground.xml | 34 ---- .../res/drawable/ic_launcher_background.xml | 170 ----------------- .../app/src/main/res/layout/activity_main.xml | 19 -- .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3056 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 5024 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2096 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2858 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4569 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 7098 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 6464 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 10676 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 9250 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 15523 -> 0 bytes .../app/src/main/res/values/colors.xml | 6 - .../app/src/main/res/values/strings.xml | 3 - .../app/src/main/res/values/styles.xml | 11 -- .../com/example/zerotier/ExampleUnitTest.java | 17 -- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 54708 -> 0 bytes ports/android/gradlew | 172 ------------------ ports/android/gradlew.bat | 84 --------- 52 files changed, 116 insertions(+), 596 deletions(-) mode change 100644 => 100755 dist.sh mode change 100644 => 100755 examples/android/ExampleAndroidApp/gradlew mode change 100755 => 100644 examples/android/ExampleAndroidApp/gradlew.bat create mode 100644 examples/python/zt.i create mode 100644 examples/rust/binding-example/Cargo.lock create mode 100644 examples/rust/binding-example/Cargo.toml create mode 100644 examples/rust/binding-example/build.rs create mode 100644 examples/rust/binding-example/src/main.rs create mode 100644 examples/rust/program.c create mode 100644 examples/rust/program.h create mode 100644 examples/rust/program.rs rename {ports => pkg}/android/.gitignore (100%) rename {ports => pkg}/android/.project (100%) rename {ports => pkg}/android/.settings/org.eclipse.buildship.core.prefs (100%) rename {ports => pkg}/android/app/.gitignore (100%) rename {ports => pkg}/android/app/build.gradle (100%) rename {ports => pkg}/android/app/proguard-rules.pro (100%) rename {ports => pkg}/android/app/src/main/AndroidManifest.xml (100%) rename {ports => pkg}/android/app/src/main/java/com/example/zerotier/MainActivity.java (100%) create mode 100644 pkg/android/app/src/main/java/com/zerotier/libzt/README rename {ports => pkg}/android/build.gradle (89%) rename {ports => pkg}/android/gradle.properties (100%) rename {ports => pkg}/android/gradle/wrapper/gradle-wrapper.properties (87%) rename gradlew => pkg/android/gradlew (100%) mode change 100644 => 100755 rename gradlew.bat => pkg/android/gradlew.bat (100%) mode change 100755 => 100644 rename {ports => pkg}/android/settings.gradle (100%) delete mode 100644 ports/android/app/src/androidTest/java/com/example/zerotier/ExampleInstrumentedTest.java delete mode 100644 ports/android/app/src/main/java/com/zerotier/libzt/README delete mode 100644 ports/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml delete mode 100644 ports/android/app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 ports/android/app/src/main/res/layout/activity_main.xml delete mode 100644 ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 ports/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 ports/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 ports/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 ports/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 ports/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 ports/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 ports/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 ports/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 ports/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 ports/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 ports/android/app/src/main/res/values/colors.xml delete mode 100644 ports/android/app/src/main/res/values/strings.xml delete mode 100644 ports/android/app/src/main/res/values/styles.xml delete mode 100644 ports/android/app/src/test/java/com/example/zerotier/ExampleUnitTest.java delete mode 100644 ports/android/gradle/wrapper/gradle-wrapper.jar delete mode 100644 ports/android/gradlew delete mode 100755 ports/android/gradlew.bat diff --git a/Makefile b/Makefile index c97aa25..0806da5 100644 --- a/Makefile +++ b/Makefile @@ -28,8 +28,9 @@ clean_ios: clean_macos: -rm -rf ports/xcode_macos clean_android: - -rm -rf ports/android/app/build - -find ports -name ".externalNativeBuild" -exec rm -r "{}" \; + -rm -rf pkg/android/app/build + -find pkg -name ".externalNativeBuild" -exec rm -r "{}" \; + $(DIST_BUILD_SCRIPT) android "clean" clean_products: -rm -rf products .PHONY: clean @@ -49,8 +50,6 @@ android_release: $(DIST_BUILD_SCRIPT) android "release" $(DIST_BUILD_SCRIPT) clean_android_project $(DIST_BUILD_SCRIPT) prep_android_example "release" -android_clean: - $(DIST_BUILD_SCRIPT) android "clean" android: android_debug android_release prep_android_debug_example: $(DIST_BUILD_SCRIPT) prep_android_example "debug" diff --git a/dist.sh b/dist.sh old mode 100644 new mode 100755 index c1b56c8..97b0c40 --- a/dist.sh +++ b/dist.sh @@ -38,7 +38,7 @@ BUILD_CONCURRENCY= #"-j 2" OSNAME=$(uname | tr '[A-Z]' '[a-z]') BUILD_TMP=$(pwd)/tmp -ANDROID_PROJ_DIR=$(pwd)/ports/android +ANDROID_PROJ_DIR=$(pwd)/pkg/android XCODE_IOS_PROJ_DIR=$(pwd)/ports/xcode_ios XCODE_IOS_SIMULATOR_PROJ_DIR=$(pwd)/ports/xcode_ios_simulator XCODE_MACOS_PROJ_DIR=$(pwd)/ports/xcode_macos @@ -328,45 +328,49 @@ host() # Set important variables for Android builds set_android_env() { - #JDK=jdk1.8.0_202.jdk # Set ANDROID_HOME because setting sdk.dir in local.properties isn't always reliable - export ANDROID_HOME=/Users/$USER/Library/Android/sdk - export PATH=/Library/Java/JavaVirtualMachines/$JDK/Contents/Home/bin/:${PATH} - export PATH=/Users/$USER/Library/Android/sdk/platform-tools/:${PATH} + #export PATH=/Library/Java/JavaVirtualMachines/$JDK/Contents/Home/bin/:${PATH} + #export PATH=/Users/$USER/Library/Android/sdk/platform-tools/:${PATH} GRADLE_ARGS=--stacktrace - ANDROID_APP_NAME=com.example.mynewestapplication + #ANDROID_APP_NAME=com.example.mynewestapplication + # for our purposes we limit this to execution on macOS + if [[ $OSNAME = *"linux"* ]]; then + export ANDROID_HOME=/usr/lib/android-sdk/ + fi + if [[ $OSNAME = *"darwin"* ]]; then + export ANDROID_HOME=/Users/$USER/Library/Android/sdk + fi } -# Build android AAR from ports/android +# Build android AAR android() { - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - set_android_env - copy_root_java_sources_to_projects - # NOTE: There's no reason this won't build on linux, it's just that - # for our purposes we limit this to execution on macOS - if [[ ! $OSNAME = *"darwin"* ]]; then - exit 0 - fi - # CMake build files - BUILD_DIR=$(pwd)/tmp/android-$1 - mkdir -p $BUILD_DIR - # If clean requested, remove temp build dir - if [[ $1 = *"clean"* ]]; then - rm -rf $BUILD_DIR - exit 0 - fi - # Where to place results - LIB_OUTPUT_DIR=$(pwd)/lib/$1/android - mkdir -p $LIB_OUTPUT_DIR - # Build - UPPERCASE_CONFIG="$(tr '[:lower:]' '[:upper:]' <<< ${1:0:1})${1:1}" - CMAKE_FLAGS="-DSDK_JNI=1 -DSDK_JNI=ON" - cd $ANDROID_PROJ_DIR - ./gradlew $GRADLE_ARGS assemble$UPPERCASE_CONFIG # assembleRelease / assembleDebug - mv $ANDROID_PROJ_DIR/app/build/outputs/aar/app-$1.aar \ - $LIB_OUTPUT_DIR/libzt-$1.aar - cd - + echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" + # Unsure why, but Gradle's build script chokes on this non-source file now + rm -rf ext/ZeroTierOne/ext/miniupnpc/VERSION + export PATH=$ANDROID_HOME/cmdline-tools/tools/bin:$PATH + set_android_env + # Copy source files into project + cp -f src/bindings/java/*.java ${ANDROID_PROJ_DIR}/app/src/main/java/com/zerotier/libzt + # CMake build files + BUILD_DIR=$(pwd)/tmp/android-$1 + mkdir -p $BUILD_DIR + # If clean requested, remove temp build dir + if [[ $1 = *"clean"* ]]; then + rm -rf $BUILD_DIR + exit 0 + fi + # Where to place results + LIB_OUTPUT_DIR=$(pwd)/lib/$1/android + mkdir -p $LIB_OUTPUT_DIR + # Build + UPPERCASE_CONFIG="$(tr '[:lower:]' '[:upper:]' <<< ${1:0:1})${1:1}" + CMAKE_FLAGS="-DSDK_JNI=1 -DSDK_JNI=ON" + cd $ANDROID_PROJ_DIR + ./gradlew $GRADLE_ARGS assemble$UPPERCASE_CONFIG # assembleRelease / assembleDebug + mv $ANDROID_PROJ_DIR/app/build/outputs/aar/*.aar \ + $LIB_OUTPUT_DIR/libzt-$1.aar + cd - } # Remove intermediate object files and/or libraries @@ -493,8 +497,7 @@ android_app_log_filtered() # based projects. copy_root_java_sources_to_projects() { - cp -f src/java/*.java ports/android/app/src/main/java/com/zerotier/libzt - cp -f src/java/*.java ports/java/com/zerotier/libzt/ + cp -f src/bindings/java/*.java ports/java/com/zerotier/libzt/ } # At the end of build stage, print contents and trees for inspection diff --git a/examples/android/ExampleAndroidApp/gradle/wrapper/gradle-wrapper.properties b/examples/android/ExampleAndroidApp/gradle/wrapper/gradle-wrapper.properties index 8333aea..d1dd0fc 100644 --- a/examples/android/ExampleAndroidApp/gradle/wrapper/gradle-wrapper.properties +++ b/examples/android/ExampleAndroidApp/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/examples/android/ExampleAndroidApp/gradlew b/examples/android/ExampleAndroidApp/gradlew old mode 100644 new mode 100755 diff --git a/examples/android/ExampleAndroidApp/gradlew.bat b/examples/android/ExampleAndroidApp/gradlew.bat old mode 100755 new mode 100644 diff --git a/examples/python/zt.i b/examples/python/zt.i new file mode 100644 index 0000000..69aa836 --- /dev/null +++ b/examples/python/zt.i @@ -0,0 +1,30 @@ +/* libzt.i */ + +%begin +%{ +#define SWIG_PYTHON_CAST_MODE +%} + +%include + +#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" diff --git a/examples/rust/binding-example/Cargo.lock b/examples/rust/binding-example/Cargo.lock new file mode 100644 index 0000000..7c0a5dd --- /dev/null +++ b/examples/rust/binding-example/Cargo.lock @@ -0,0 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "binding-example" +version = "0.1.0" diff --git a/examples/rust/binding-example/Cargo.toml b/examples/rust/binding-example/Cargo.toml new file mode 100644 index 0000000..fe94196 --- /dev/null +++ b/examples/rust/binding-example/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "binding-example" +version = "0.1.0" +authors = ["Joseph Henry "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +#rustc-link-search = ["../../../lib/debug/macos-x86_64/"] + +[dependencies] diff --git a/examples/rust/binding-example/build.rs b/examples/rust/binding-example/build.rs new file mode 100644 index 0000000..bf44483 --- /dev/null +++ b/examples/rust/binding-example/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rustc-flags=-l dylib=c++"); + //println!("cargo:rustc-link-search=."); +} \ No newline at end of file diff --git a/examples/rust/binding-example/src/main.rs b/examples/rust/binding-example/src/main.rs new file mode 100644 index 0000000..cd9f0fd --- /dev/null +++ b/examples/rust/binding-example/src/main.rs @@ -0,0 +1,10 @@ + +#[link(name = "libzt", kind = "dylib")] +extern { + fn zts_socket(address_family: i32) -> i32; +} + +fn main() { + let x = unsafe { zts_socket(100) }; + println!("zts_socket() = {}", x); +} diff --git a/examples/rust/program.c b/examples/rust/program.c new file mode 100644 index 0000000..816f383 --- /dev/null +++ b/examples/rust/program.c @@ -0,0 +1,6 @@ +#include + +int zts_socket(int address_family) +{ + return -777; +} diff --git a/examples/rust/program.h b/examples/rust/program.h new file mode 100644 index 0000000..23847a0 --- /dev/null +++ b/examples/rust/program.h @@ -0,0 +1 @@ +int zts_socket(int address_family); diff --git a/examples/rust/program.rs b/examples/rust/program.rs new file mode 100644 index 0000000..169c9dd --- /dev/null +++ b/examples/rust/program.rs @@ -0,0 +1,5 @@ +/* automatically generated by rust-bindgen 0.56.0 */ + +extern "C" { + pub fn zts_socket(address_family: ::std::os::raw::c_int) -> ::std::os::raw::c_int; +} diff --git a/ports/android/.gitignore b/pkg/android/.gitignore similarity index 100% rename from ports/android/.gitignore rename to pkg/android/.gitignore diff --git a/ports/android/.project b/pkg/android/.project similarity index 100% rename from ports/android/.project rename to pkg/android/.project diff --git a/ports/android/.settings/org.eclipse.buildship.core.prefs b/pkg/android/.settings/org.eclipse.buildship.core.prefs similarity index 100% rename from ports/android/.settings/org.eclipse.buildship.core.prefs rename to pkg/android/.settings/org.eclipse.buildship.core.prefs diff --git a/ports/android/app/.gitignore b/pkg/android/app/.gitignore similarity index 100% rename from ports/android/app/.gitignore rename to pkg/android/app/.gitignore diff --git a/ports/android/app/build.gradle b/pkg/android/app/build.gradle similarity index 100% rename from ports/android/app/build.gradle rename to pkg/android/app/build.gradle diff --git a/ports/android/app/proguard-rules.pro b/pkg/android/app/proguard-rules.pro similarity index 100% rename from ports/android/app/proguard-rules.pro rename to pkg/android/app/proguard-rules.pro diff --git a/ports/android/app/src/main/AndroidManifest.xml b/pkg/android/app/src/main/AndroidManifest.xml similarity index 100% rename from ports/android/app/src/main/AndroidManifest.xml rename to pkg/android/app/src/main/AndroidManifest.xml diff --git a/ports/android/app/src/main/java/com/example/zerotier/MainActivity.java b/pkg/android/app/src/main/java/com/example/zerotier/MainActivity.java similarity index 100% rename from ports/android/app/src/main/java/com/example/zerotier/MainActivity.java rename to pkg/android/app/src/main/java/com/example/zerotier/MainActivity.java diff --git a/pkg/android/app/src/main/java/com/zerotier/libzt/README b/pkg/android/app/src/main/java/com/zerotier/libzt/README new file mode 100644 index 0000000..4d871fb --- /dev/null +++ b/pkg/android/app/src/main/java/com/zerotier/libzt/README @@ -0,0 +1 @@ +Before build, Java wrapper sources will be copied here diff --git a/ports/android/build.gradle b/pkg/android/build.gradle similarity index 89% rename from ports/android/build.gradle rename to pkg/android/build.gradle index 2426268..43c0708 100644 --- a/ports/android/build.gradle +++ b/pkg/android/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:3.1.3' // NOTE: Do not place your application dependencies here; they belong diff --git a/ports/android/gradle.properties b/pkg/android/gradle.properties similarity index 100% rename from ports/android/gradle.properties rename to pkg/android/gradle.properties diff --git a/ports/android/gradle/wrapper/gradle-wrapper.properties b/pkg/android/gradle/wrapper/gradle-wrapper.properties similarity index 87% rename from ports/android/gradle/wrapper/gradle-wrapper.properties rename to pkg/android/gradle/wrapper/gradle-wrapper.properties index 472bb68..9fe8d05 100644 --- a/ports/android/gradle/wrapper/gradle-wrapper.properties +++ b/pkg/android/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,3 @@ -#Wed Jul 18 09:57:38 PDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/pkg/android/gradlew old mode 100644 new mode 100755 similarity index 100% rename from gradlew rename to pkg/android/gradlew diff --git a/gradlew.bat b/pkg/android/gradlew.bat old mode 100755 new mode 100644 similarity index 100% rename from gradlew.bat rename to pkg/android/gradlew.bat diff --git a/ports/android/settings.gradle b/pkg/android/settings.gradle similarity index 100% rename from ports/android/settings.gradle rename to pkg/android/settings.gradle diff --git a/ports/android/app/src/androidTest/java/com/example/zerotier/ExampleInstrumentedTest.java b/ports/android/app/src/androidTest/java/com/example/zerotier/ExampleInstrumentedTest.java deleted file mode 100644 index fe8e36c..0000000 --- a/ports/android/app/src/androidTest/java/com/example/zerotier/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.example.zerotier; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - assertEquals("com.example.zerotier", appContext.getPackageName()); - } -} diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/README b/ports/android/app/src/main/java/com/zerotier/libzt/README deleted file mode 100644 index 062ccae..0000000 --- a/ports/android/app/src/main/java/com/zerotier/libzt/README +++ /dev/null @@ -1 +0,0 @@ -Before build, Java sources are copied here from src/java in the root directory of this repo. diff --git a/ports/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/ports/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index c7bd21d..0000000 --- a/ports/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/ports/android/app/src/main/res/drawable/ic_launcher_background.xml b/ports/android/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index d5fccc5..0000000 --- a/ports/android/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ports/android/app/src/main/res/layout/activity_main.xml b/ports/android/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 7d8d24e..0000000 --- a/ports/android/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index eca70cf..0000000 --- a/ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index eca70cf..0000000 --- a/ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/ports/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/ports/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a2f5908281d070150700378b64a84c7db1f97aa1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3056 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 axPAxe%7rDusX+w*00006jj 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⁣ zwiG+x#%j}{0gU~J8;<|!B1@-XaB@{KORFwrYg_8rOv({b0EO#DbeQRm;B6_9=mXGf z-x|VL{zd`)#@yN}HkCSJbjbNlE|zL3Wm9Q8HY`sV)}3%pgN>cL^67{Z;PPL(*wT8N zUjXU{@|*hvm}({wsAC=x0^ok0%UAz0;sogW{B!nDqk|JJ5x~4NfTDgP49^zeu`csl?5mY@JdQdISc zFs!E{^grmkLnUk9 zny~m)1vws@5BFI<-0Tuo2JWX(0v`W|t(wg;s--L47WTvTMz-8l#TL^=OJNRS2?_Qj z3AKT+gvbyBi#H*-tJ%tWD|>EV3wy|8qxfzS!5RW;Jpl5*zo&^UBU=fG#2}UvRyNkK zA06Dy9;K1ca@r2T>yThYgI!ont$(G{6q#2QT+00r_x0(b)gsE`lBB?2gr55gq^D3Fi&p%E(p9>U%bv zkg1Jco(RbyTX7FDHOnl7-O@ zI$AaIl?9NJKPm(WiBP`1-#CB1QzU>&hKm)fpa5DKE{2$X0hGz-0uZ?cyTk(YC!Y&| zL=1VrNERSA5NA2jq7FACfX4JfPyj5XXl1yv0>~s;eF7L2$>&oMqeTFT2m$y7FlkON z_yurD1yIOvA;5C6016pyxBznGUt0kJ&k5r#;&>Jow`r)sp9R~PmK~lz$3xH%LT*1U zJdOyABZ3!FvNoR*vN$5ykHS8f`jA4zV+|L}i1C4`B2c{R0;UdYxaU|H)2avz@ z=mEYc|2S<+(B2Tj+FkX+2D+yFI!k9lWMA61DJ{)e;lum$(;O87?vGJJe!KtK04+N_ zI*P~t@dUb>9Xh{dbyl{-ZQ(UMgz7$|QfL5XSPkskt^NgctYC#;4WcZB1@%@wy@2t3 z2z0DI7&%b$*Aw~abe?GxE`ez@+6hOh-6*8fHRV{1os$EL@}uUZeG4h1&Be`98q*7j z=3-v+lhIjfWVo12!<>%V^a6lTgW3+_#W6n|p*~==zOH7z$0{LSZk(Tpd7EaD04hnA zL;#fxS0aD{`5^&D`}>0Uq?byDD-l2=!wm_bLcUl4gc(% za1p|itVANvFF>hghAS07Im1;IK;|b*W)}VDyI;BIp2=K*yu2a)j?B|f<44NI$NbmJ z#dE0>jI$fMr&@>4kN8MLFb4&2O9fEKaQg%(QO$4_1rVQywG^CmBLh#}_7gKW3vd?| z2?1^&KWq8}8I^_S0|)MowU_pw$q@nl@Nkn$z>BQq_KA^9yaR`(R3u{{Ig;cwt z@AJ^{ODQCm^neroM9nKNUAXi9RCK`OsP_LuR0PUR(YZCCX5dNF6VzcoK&=b^r`W?ltt|*F zpkoae%ZT{C1h~EcFui~b7fF`vb<<~j_VquuUA$}QqIKYELPp#;{u?q8Dz}WAG-(3; zjrm$i%7UbyZMM(Y{>!uJ#vNB?R~B{6Htp=>e*<{fQQ5W7V(1coCWlOON!MzZxhum| ztZBQpGR z;~#ur^&PockKdV{Q6R>o`Pl{0x!DEbpZ7y9Y;*ZvE!*gU`V1W3znva{f=?WO5I&>B z&hw6}tjECtaghm5z|C#%M;Yf_*pI^};h}Vl=^r9EN=tVDj86D;C$jIJ?K7VP+00000NkvXXu0mjf D5i!M* diff --git a/ports/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/ports/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 459ca609d3ae0d3943ab44cdc27feef9256dc6d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7098 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 diff --git a/ports/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/ports/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8ca12fe024be86e868d14e91120a6902f8e88ac6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6464 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 B0*L?s diff --git a/ports/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/ports/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 8e19b410a1b15ff180f3dacac19395fe3046cdec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10676 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 diff --git a/ports/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/ports/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 4c19a13c239cb67b8a2134ddd5f325db1d2d5bee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15523 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 BIP(Ai diff --git a/ports/android/app/src/main/res/values/colors.xml b/ports/android/app/src/main/res/values/colors.xml deleted file mode 100644 index 3ab3e9c..0000000 --- a/ports/android/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - #3F51B5 - #303F9F - #FF4081 - diff --git a/ports/android/app/src/main/res/values/strings.xml b/ports/android/app/src/main/res/values/strings.xml deleted file mode 100644 index 87c6cc8..0000000 --- a/ports/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - ZeroTier - diff --git a/ports/android/app/src/main/res/values/styles.xml b/ports/android/app/src/main/res/values/styles.xml deleted file mode 100644 index 5885930..0000000 --- a/ports/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/ports/android/app/src/test/java/com/example/zerotier/ExampleUnitTest.java b/ports/android/app/src/test/java/com/example/zerotier/ExampleUnitTest.java deleted file mode 100644 index f0aee30..0000000 --- a/ports/android/app/src/test/java/com/example/zerotier/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.zerotier; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/ports/android/gradle/wrapper/gradle-wrapper.jar b/ports/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 7a3265ee94c0ab25cf079ac8ccdf87f41d455d42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54708 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

_x$+l{{cH8$W#CT diff --git a/ports/android/gradlew b/ports/android/gradlew deleted file mode 100644 index cccdd3d..0000000 --- a/ports/android/gradlew +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env sh - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/ports/android/gradlew.bat b/ports/android/gradlew.bat deleted file mode 100755 index e95643d..0000000 --- a/ports/android/gradlew.bat +++ /dev/null @@ -1,84 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega From d4525a39c71be34ea3bfe0ca09ed2d46c28f7107 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 15 Feb 2021 09:23:33 -0800 Subject: [PATCH 03/25] Remove unnecessary DLL versioning file --- version.rc.in | 43 ------------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 version.rc.in diff --git a/version.rc.in b/version.rc.in deleted file mode 100644 index aefd247..0000000 --- a/version.rc.in +++ /dev/null @@ -1,43 +0,0 @@ -#define VER_FILEVERSION @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_PATCH@,0 -#define VER_FILEVERSION_STR "@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@.0\0" - -#define VER_PRODUCTVERSION @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_PATCH@,0 -#define VER_PRODUCTVERSION_STR "@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@\0" - -#define VER_PRODUCTVERSION VER_FILEVERSION -#define VER_PRODUCTVERSION_STR VER_FILEVERSION_STR - -#define APSTUDIO_READONLY_SYMBOLS -#include "windows.h" -#undef APSTUDIO_READONLY_SYMBOLS - -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#pragma code_page(1252) - -VS_VERSION_INFO VERSIONINFO - FILEVERSION VER_FILEVERSION - FILEFLAGSMASK 0x3fL - FILEFLAGS 0x0L - FILEOS 0x40004L - FILETYPE 0x1L - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904E4" - BEGIN - VALUE "CompanyName", "ZeroTier, Inc.\0" - VALUE "FileDescription", "ZeroTier SDK\0" - VALUE "InternalName", "zt\0" - VALUE "LegalCopyright", "Copyright (C) 2020 ZeroTier, Inc.\0" - VALUE "OriginalFilename", "libzt.dll\0" - VALUE "PrivateBuild", "0\0" - VALUE "ProductName", "libzt\0" - VALUE "ProductVersion", VER_PRODUCTVERSION_STR - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1252 - END -END \ No newline at end of file From 02a401bf7f9d62be92eb8aacbf772738dedabcbb Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 16 Feb 2021 00:14:26 -0800 Subject: [PATCH 04/25] Update C# wrapper (Namespace restructure, API additions, memory leak fix) --- examples/csharp/example.cs | 75 ++++--- .../ZeroTier.Sockets.x64.nuspec | 2 +- pkg/nuget/ZeroTier.Sockets/nuget.org.md | 29 +++ src/bindings/csharp/Constants.cs | 2 +- src/bindings/csharp/Event.cs | 2 +- src/bindings/csharp/Node.cs | 84 ++++---- src/bindings/csharp/Socket.cs | 185 +++++++++++++----- src/bindings/csharp/SocketException.cs | 2 +- src/bindings/csharp/zt_wrap.cxx | 4 +- 9 files changed, 264 insertions(+), 121 deletions(-) create mode 100644 pkg/nuget/ZeroTier.Sockets/nuget.org.md diff --git a/examples/csharp/example.cs b/examples/csharp/example.cs index d717275..5be0095 100644 --- a/examples/csharp/example.cs +++ b/examples/csharp/example.cs @@ -5,24 +5,39 @@ using System.Net; using System.Net.Sockets; // For SocketType, etc using System.Text; // For Encoding -using ZeroTier; // For ZeroTier.Node, ZeroTier.Event, and ZeroTier.Socket +/** + * + * Namespaces explained: + * + * ZeroTier.Core (API to control a ZeroTier Node) + * -> class ZeroTier.Core.Node + * -> class ZeroTier.Core.Event + * + * ZeroTier.Sockets (Socket API similar to System.Net.Sockets) + * -> class ZeroTier.Sockets.Socket + * -> class ZeroTier.Sockets.SocketException + * + * ZeroTier.Central (upcoming) + * + */ +using ZeroTier; public class ExampleApp { - ZeroTier.Node node; + ZeroTier.Core.Node node; /** * Initialize and start ZeroTier */ public void StartZeroTier(string configFilePath, ushort servicePort, ulong networkId) { - node = new ZeroTier.Node(configFilePath, OnZeroTierEvent, servicePort); + node = new ZeroTier.Core.Node(configFilePath, OnZeroTierEvent, servicePort); node.Start(); // Network activity only begins after calling Start() /* How you do this next part is up to you, but essentially we're waiting for the node - to signal to us (via a ZeroTier.Event) that it has access to the internet and is - able to talk to one of our root servers. As a convenience you can just periodically check - IsOnline() instead of looking for the event via the callback. */ + to signal to us via OnZeroTierEvent(ZeroTier.Core.Event) that it has access to the + internet and is able to talk to one of our root servers. As a convenience you can just + periodically check Node.IsOnline() instead of looking for the event via the callback. */ while (!node.IsOnline()) { Thread.Sleep(100); } /* After the node comes online you may now join/leave networks. You will receive @@ -31,7 +46,7 @@ public class ExampleApp { or removed routes, etc. */ node.Join(networkId); - /* Note that ZeroTier.Socket calls will fail if there are no routes available, for this + /* Note that ZeroTier.Sockets.Socket calls will fail if there are no routes available, for this reason we should wait to make those calls until the node has indicated to us that at least one network has been joined successfully. */ while (!node.HasRoutes()) { Thread.Sleep(100); } @@ -49,7 +64,7 @@ public class ExampleApp { * Your application should process event messages and return control as soon as possible. Blocking * or otherwise time-consuming operations are not reccomended here. */ - public void OnZeroTierEvent(ZeroTier.Event e) + public void OnZeroTierEvent(ZeroTier.Core.Event e) { Console.WriteLine("Event.eventCode = {0} ({1})", e.EventCode, e.EventName); @@ -73,7 +88,7 @@ public class ExampleApp { byte[] bytes = new Byte[1024]; Console.WriteLine(localEndPoint.ToString()); - ZeroTier.Socket listener = new ZeroTier.Socket(AddressFamily.InterNetwork, + ZeroTier.Sockets.Socket listener = new ZeroTier.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp ); // Bind the socket to the local endpoint and @@ -89,13 +104,13 @@ public class ExampleApp { // Program is suspended while waiting for an incoming connection. bool nonblocking = true; - ZeroTier.Socket handler; + ZeroTier.Sockets.Socket handler; if (nonblocking) { // Non-blocking style Accept() loop using Poll() Console.WriteLine("Starting non-blocking Accept() loop..."); listener.Blocking = false; // loop - int timeout = 1000000; // microseconds (1 second) + int timeout = 100000; // microseconds (1 second) while (true) { Console.WriteLine("Polling... (for data or incoming connections)"); if (listener.Poll(timeout, SelectMode.SelectRead)) { @@ -103,7 +118,7 @@ public class ExampleApp { handler = listener.Accept(); break; } - Thread.Sleep(1000); + //Thread.Sleep(5); } } else { // Blocking style @@ -113,26 +128,40 @@ public class ExampleApp { data = null; Console.WriteLine("Accepted connection from: " + handler.RemoteEndPoint.ToString()); + // handler.ReceiveTimeout = 1000; + // An incoming connection needs to be processed. while (true) { - int bytesRec = handler.Receive(bytes); - Console.WriteLine("Bytes received: {0}", bytesRec); - data += Encoding.ASCII.GetString(bytes,0,bytesRec); - + int bytesRec = 0; + try { + Console.WriteLine("Receiving..."); + bytesRec = handler.Receive(bytes); + } + catch (ZeroTier.Sockets.SocketException e) + { + Console.WriteLine("ServiveErrorCode={0} SocketErrorCode={1}", e.ServiceErrorCode, e.SocketErrorCode); + } if (bytesRec > 0) { + Console.WriteLine("Bytes received: {0}", bytesRec); + data = Encoding.ASCII.GetString(bytes,0,bytesRec); Console.WriteLine( "Text received : {0}", data); - break; + //break; + // Echo the data back to the client. + byte[] msg = Encoding.ASCII.GetBytes(data); + handler.Send(msg); + } + else + { + System.GC.Collect(); + Console.WriteLine("No data..."); } } - // Echo the data back to the client. - byte[] msg = Encoding.ASCII.GetBytes(data); - handler.Send(msg); handler.Shutdown(SocketShutdown.Both); handler.Close(); } - } catch (ZeroTier.SocketException e) { + } catch (ZeroTier.Sockets.SocketException e) { Console.WriteLine(e); Console.WriteLine("ServiveErrorCode={0} SocketErrorCode={1}", e.ServiceErrorCode, e.SocketErrorCode); } @@ -151,7 +180,7 @@ public class ExampleApp { // Connect to a remote device. try { // Create a TCP/IP socket. - ZeroTier.Socket sender = new ZeroTier.Socket(AddressFamily.InterNetwork, + ZeroTier.Sockets.Socket sender = new ZeroTier.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp ); // Connect the socket to the remote endpoint. Catch any errors. @@ -181,7 +210,7 @@ public class ExampleApp { } catch (ArgumentNullException ane) { Console.WriteLine("ArgumentNullException : {0}",ane.ToString()); - } catch (ZeroTier.SocketException e) { + } catch (ZeroTier.Sockets.SocketException e) { Console.WriteLine(e); Console.WriteLine("ServiveErrorCode={0} SocketErrorCode={1}", e.ServiceErrorCode, e.SocketErrorCode); } diff --git a/pkg/nuget/ZeroTier.Sockets/ZeroTier.Sockets.x64.nuspec b/pkg/nuget/ZeroTier.Sockets/ZeroTier.Sockets.x64.nuspec index bdf61b8..5502015 100644 --- a/pkg/nuget/ZeroTier.Sockets/ZeroTier.Sockets.x64.nuspec +++ b/pkg/nuget/ZeroTier.Sockets/ZeroTier.Sockets.x64.nuspec @@ -9,7 +9,7 @@ LICENSE.txt icon.png false - Initial release + Namespace adjustments, additions to Socket API, memory leak fixes. Encrypted P2P SD-WAN networking layer (Managed C# API) [x64] Encrypted P2P SD-WAN networking layer (Managed C# API) [x64] Copyright 2021 ZeroTier, Inc. diff --git a/pkg/nuget/ZeroTier.Sockets/nuget.org.md b/pkg/nuget/ZeroTier.Sockets/nuget.org.md new file mode 100644 index 0000000..91f0b04 --- /dev/null +++ b/pkg/nuget/ZeroTier.Sockets/nuget.org.md @@ -0,0 +1,29 @@ +[ZeroTier](https://www.zerotier.com) SDK +===== + +Connect physical devices, virtual devices, and application instances as if everything is on a single LAN. + +ZeroTier brings your network into user-space. We've paired our network hypervisor core with a TCP/UDP/IP stack [(lwIP)](https://en.wikipedia.org/wiki/LwIP) to provide your application with an exclusive and private virtual network interface. All traffic on this interface is end-to-end encrypted between each peer and we provide an easy-to-use socket interface similar to [Berkeley Sockets](https://en.wikipedia.org/wiki/Berkeley_sockets). Since we aren't using the kernel's IP stack that means no drivers, no root, and no host configuration requirements. + + - Website: https://www.zerotier.com/ + - ZeroTier Manual: https://www.zerotier.com/manual/ + - ZeroTier Repo: https://github.com/zerotier/zerotierone + - SDK Repo: https://github.com/zerotier/libzt + - Forum: https://discuss.zerotier.com + +## 1.3.3-alpha.2 Release Notes + +### New namespace structure: +- `ZeroTier.Core` (API to control a ZeroTier Node) + - `class ZeroTier.Core.Node` + - `class ZeroTier.Core.Event` +- `ZeroTier.Sockets` + - `class ZeroTier.Sockets.Socket` + - `class ZeroTier.Sockets.SocketException` +- `ZeroTier.Central` (upcoming) + +### Added to Socket API + - `Socket.ReceiveTimeout`, `Socket.SendTimeout`, etc. + +### Bugs + - Fixed memory leak caused by unmanaged resources not being released. \ No newline at end of file diff --git a/src/bindings/csharp/Constants.cs b/src/bindings/csharp/Constants.cs index 69dcb3a..e7c6882 100644 --- a/src/bindings/csharp/Constants.cs +++ b/src/bindings/csharp/Constants.cs @@ -250,7 +250,7 @@ namespace ZeroTier public static readonly short MSG_DONTWAIT = 0x0008; public static readonly short MSG_MORE = 0x0010; - // Macro's for defining ioctl() command values + // Macros for defining ioctl() command values /* public static readonly ulong IOCPARM_MASK = 0x7fU; public static readonly ulong IOC_VOID = 0x20000000UL; diff --git a/src/bindings/csharp/Event.cs b/src/bindings/csharp/Event.cs index db5e18d..f5ed712 100644 --- a/src/bindings/csharp/Event.cs +++ b/src/bindings/csharp/Event.cs @@ -15,7 +15,7 @@ using System.Net; using ZeroTier; -namespace ZeroTier +namespace ZeroTier.Core { /* Convenience structures for exposing lower level operational details to the user. These structures do not have the same memory layout or content as those found in diff --git a/src/bindings/csharp/Node.cs b/src/bindings/csharp/Node.cs index 3771af7..1be62b6 100644 --- a/src/bindings/csharp/Node.cs +++ b/src/bindings/csharp/Node.cs @@ -14,6 +14,8 @@ using System.Runtime.InteropServices; using System; +using ZeroTier; + // Prototype of callback used by ZeroTier to signal events to C# application [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void CSharpCallbackWithStruct(IntPtr msgPtr); @@ -21,9 +23,9 @@ public delegate void CSharpCallbackWithStruct(IntPtr msgPtr); ///

/// ZeroTier SDK /// -namespace ZeroTier +namespace ZeroTier.Core { - public delegate void ZeroTierManagedEventCallback(ZeroTier.Event nodeEvent); + public delegate void ZeroTierManagedEventCallback(ZeroTier.Core.Event nodeEvent); ///
/// ZeroTier Node - Virtual network subsystem @@ -45,11 +47,11 @@ namespace ZeroTier zts_callback_msg msg = (zts_callback_msg)Marshal.PtrToStructure(msgPtr, typeof(zts_callback_msg)); - ZeroTier.Event newEvent = null; + ZeroTier.Core.Event newEvent = null; // Node events if (msg.eventCode == Constants.EVENT_NODE_UP) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NODE_UP"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NODE_UP"); } if (msg.eventCode == Constants.EVENT_NODE_ONLINE) { _isOnline = true; @@ -57,135 +59,135 @@ namespace ZeroTier zts_node_details details = (zts_node_details)Marshal.PtrToStructure(msg.node, typeof(zts_node_details)); _nodeId = details.address; - newEvent = new ZeroTier.Event(msg.eventCode, "EVENT_NODE_ONLINE"); + newEvent = new ZeroTier.Core.Event(msg.eventCode, "EVENT_NODE_ONLINE"); } if (msg.eventCode == Constants.EVENT_NODE_OFFLINE) { _isOnline = false; - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NODE_OFFLINE"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NODE_OFFLINE"); } if (msg.eventCode == Constants.EVENT_NODE_NORMAL_TERMINATION) { _isOnline = false; - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NODE_NORMAL_TERMINATION"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NODE_NORMAL_TERMINATION"); } if (msg.eventCode == Constants.EVENT_NODE_DOWN) { _isOnline = false; - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NODE_DOWN"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NODE_DOWN"); } if (msg.eventCode == Constants.EVENT_NODE_IDENTITY_COLLISION) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NODE_IDENTITY_COLLISION"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NODE_IDENTITY_COLLISION"); _isOnline = false; } if (msg.eventCode == Constants.EVENT_NODE_UNRECOVERABLE_ERROR) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NODE_UNRECOVERABLE_ERROR"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NODE_UNRECOVERABLE_ERROR"); _isOnline = false; } // Network events if (msg.eventCode == Constants.EVENT_NETWORK_NOT_FOUND) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_NOT_FOUND"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETWORK_NOT_FOUND"); } if (msg.eventCode == Constants.EVENT_NETWORK_REQ_CONFIG) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_REQ_CONFIG"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETWORK_REQ_CONFIG"); } if (msg.eventCode == Constants.EVENT_NETWORK_ACCESS_DENIED) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_ACCESS_DENIED"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETWORK_ACCESS_DENIED"); } if (msg.eventCode == Constants.EVENT_NETWORK_READY_IP4) { _joinedAtLeastOneNetwork = true; - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_READY_IP4"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETWORK_READY_IP4"); } if (msg.eventCode == Constants.EVENT_NETWORK_READY_IP6) { _joinedAtLeastOneNetwork = true; - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_READY_IP6"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETWORK_READY_IP6"); } if (msg.eventCode == Constants.EVENT_NETWORK_DOWN) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_DOWN"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETWORK_DOWN"); } if (msg.eventCode == Constants.EVENT_NETWORK_CLIENT_TOO_OLD) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_CLIENT_TOO_OLD"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETWORK_CLIENT_TOO_OLD"); } if (msg.eventCode == Constants.EVENT_NETWORK_REQ_CONFIG) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_REQ_CONFIG"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETWORK_REQ_CONFIG"); } if (msg.eventCode == Constants.EVENT_NETWORK_OK) { zts_network_details unmanagedDetails = (zts_network_details)Marshal.PtrToStructure(msg.network, typeof(zts_network_details)); - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_OK"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETWORK_OK"); newEvent.networkDetails = new NetworkDetails(); newEvent.networkDetails.networkId = unmanagedDetails.nwid; } if (msg.eventCode == Constants.EVENT_NETWORK_ACCESS_DENIED) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_ACCESS_DENIED"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETWORK_ACCESS_DENIED"); } if (msg.eventCode == Constants.EVENT_NETWORK_READY_IP4_IP6) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_READY_IP4_IP6"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETWORK_READY_IP4_IP6"); } if (msg.eventCode == Constants.EVENT_NETWORK_UPDATE) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_UPDATE"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETWORK_UPDATE"); } // Stack events if (msg.eventCode == Constants.EVENT_STACK_UP) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_STACK_UP"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_STACK_UP"); } if (msg.eventCode == Constants.EVENT_STACK_DOWN) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_STACK_DOWN"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_STACK_DOWN"); } // Address events if (msg.eventCode == Constants.EVENT_ADDR_ADDED_IP4) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ADDR_ADDED_IP4"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_ADDR_ADDED_IP4"); } if (msg.eventCode == Constants.EVENT_ADDR_ADDED_IP6) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ADDR_ADDED_IP6"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_ADDR_ADDED_IP6"); } if (msg.eventCode == Constants.EVENT_ADDR_REMOVED_IP4) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ADDR_REMOVED_IP4"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_ADDR_REMOVED_IP4"); } if (msg.eventCode == Constants.EVENT_ADDR_REMOVED_IP6) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ADDR_REMOVED_IP6"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_ADDR_REMOVED_IP6"); } // peer events if (msg.eventCode == Constants.EVENT_PEER_DIRECT) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_PEER_DIRECT"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_PEER_DIRECT"); } if (msg.eventCode == Constants.EVENT_PEER_RELAY) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_PEER_RELAY"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_PEER_RELAY"); } - // newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_PEER_UNREACHABLE"); + // newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_PEER_UNREACHABLE"); if (msg.eventCode == Constants.EVENT_PEER_PATH_DISCOVERED) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_PEER_PATH_DISCOVERED"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_PEER_PATH_DISCOVERED"); } if (msg.eventCode == Constants.EVENT_PEER_PATH_DEAD) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_PEER_PATH_DEAD"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_PEER_PATH_DEAD"); } // Route events if (msg.eventCode == Constants.EVENT_ROUTE_ADDED) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ROUTE_ADDED"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_ROUTE_ADDED"); } if (msg.eventCode == Constants.EVENT_ROUTE_REMOVED) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ROUTE_REMOVED"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_ROUTE_REMOVED"); } // Netif events if (msg.eventCode == Constants.EVENT_NETIF_UP) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETIF_UP"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETIF_UP"); } if (msg.eventCode == Constants.EVENT_NETIF_DOWN) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETIF_DOWN"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETIF_DOWN"); } if (msg.eventCode == Constants.EVENT_NETIF_REMOVED) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETIF_REMOVED"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETIF_REMOVED"); } if (msg.eventCode == Constants.EVENT_NETIF_LINK_UP) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETIF_LINK_UP"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETIF_LINK_UP"); } if (msg.eventCode == Constants.EVENT_NETIF_LINK_DOWN) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETIF_LINK_DOWN"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_NETIF_LINK_DOWN"); } if (msg.eventCode == Constants.EVENT_ADDR_REMOVED_IP6) { - newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ADDR_REMOVED_IP6"); + newEvent = new ZeroTier.Core.Event(msg.eventCode,"EVENT_ADDR_REMOVED_IP6"); } // Pass the converted Event to the managed callback (visible to user) diff --git a/src/bindings/csharp/Socket.cs b/src/bindings/csharp/Socket.cs index 4de872b..2cce0d1 100644 --- a/src/bindings/csharp/Socket.cs +++ b/src/bindings/csharp/Socket.cs @@ -13,7 +13,7 @@ using System; // For ObjectDisposedException using System.Net; // For IPEndPoint -using System.Net.Sockets; // For ZeroTier.SocketException +using System.Net.Sockets; // For ZeroTier.Sockets.SocketException using System.Runtime.InteropServices; using ZeroTier; @@ -21,7 +21,7 @@ using ZeroTier; /// /// ZeroTier SDK /// -namespace ZeroTier +namespace ZeroTier.Sockets { /// /// ZeroTier Socket - An lwIP socket mediated over a ZeroTier virtual link @@ -45,6 +45,8 @@ namespace ZeroTier bool _isClosed; bool _isListening; bool _isBlocking; + bool _isBound; + bool _isConnected; AddressFamily _socketFamily; SocketType _socketType; @@ -101,7 +103,7 @@ namespace ZeroTier } if ((_fd = zts_socket(family, type, protocol)) < 0) { - throw new ZeroTier.SocketException((int)_fd); + throw new ZeroTier.Sockets.SocketException((int)_fd); } _socketFamily = addressFamily; _socketType = socketType; @@ -132,13 +134,14 @@ namespace ZeroTier } if (_fd < 0) { // Invalid file descriptor - throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET); + throw new ZeroTier.Sockets.SocketException((int)Constants.ERR_SOCKET); } if (remoteEndPoint == null) { throw new ArgumentNullException("remoteEndPoint"); } int err = Constants.ERR_OK; int addrlen = 0; + IntPtr remoteAddrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(zts_sockaddr))); if (remoteEndPoint.AddressFamily == AddressFamily.InterNetwork) { zts_sockaddr_in sa = new zts_sockaddr_in(); @@ -159,10 +162,9 @@ namespace ZeroTier sa.sin_addr = remoteEndPoint.Address.GetAddressBytes(); sa.sin_len = (byte)addrlen; // lwIP-specific - IntPtr ptr1 = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(zts_sockaddr))); - Marshal.StructureToPtr(sa, ptr1, false); - //zts_sockaddr sAddr = (zts_sockaddr)Marshal.PtrToStructure(ptr1, typeof(zts_sockaddr)); - err = zts_connect(_fd, ptr1, (byte)addrlen); + Marshal.StructureToPtr(sa, remoteAddrPtr, false); + //zts_sockaddr sAddr = (zts_sockaddr)Marshal.PtrToStructure(remoteAddrPtr, typeof(zts_sockaddr)); + err = zts_connect(_fd, remoteAddrPtr, (byte)addrlen); } if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6) @@ -179,9 +181,11 @@ namespace ZeroTier */ } if (err < 0) { - throw new ZeroTier.SocketException(err, ZeroTier.Node.ErrNo); + throw new ZeroTier.Sockets.SocketException(err, ZeroTier.Core.Node.ErrNo); } + Marshal.FreeHGlobal(remoteAddrPtr); _remoteEndPoint = remoteEndPoint; + _isConnected = true; } public void Bind(IPEndPoint localEndPoint) @@ -191,13 +195,14 @@ namespace ZeroTier } if (_fd < 0) { // Invalid file descriptor - throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET); + throw new ZeroTier.Sockets.SocketException((int)Constants.ERR_SOCKET); } if (localEndPoint == null) { throw new ArgumentNullException("localEndPoint"); } int err = Constants.ERR_OK; int addrlen = 0; + IntPtr localAddrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(zts_sockaddr))); if (localEndPoint.AddressFamily == AddressFamily.InterNetwork) { zts_sockaddr_in sa = new zts_sockaddr_in(); @@ -217,10 +222,9 @@ namespace ZeroTier sa.sin_port = (short)zts_htons((ushort)localEndPoint.Port); sa.sin_addr = localEndPoint.Address.GetAddressBytes(); sa.sin_len = (byte)addrlen; // lwIP-specific - - IntPtr ptr1 = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(zts_sockaddr))); - Marshal.StructureToPtr(sa, ptr1, false); - err = zts_bind(_fd, ptr1, (byte)addrlen); + + Marshal.StructureToPtr(sa, localAddrPtr, false); + err = zts_bind(_fd, localAddrPtr, (byte)addrlen); } if (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6) { @@ -236,9 +240,11 @@ namespace ZeroTier */ } if (err < 0) { - throw new ZeroTier.SocketException((int)err); + throw new ZeroTier.Sockets.SocketException((int)err); } + Marshal.FreeHGlobal(localAddrPtr); _localEndPoint = localEndPoint; + _isBound = true; } public void Listen(int backlog) @@ -248,12 +254,12 @@ namespace ZeroTier } if (_fd < 0) { // Invalid file descriptor - throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET); + throw new ZeroTier.Sockets.SocketException((int)Constants.ERR_SOCKET); } int err = Constants.ERR_OK; if ((err = zts_listen(_fd, backlog)) < 0) { // Invalid backlog value perhaps? - throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET); + throw new ZeroTier.Sockets.SocketException((int)Constants.ERR_SOCKET); } _isListening = true; } @@ -265,7 +271,7 @@ namespace ZeroTier } if (_fd < 0) { // Invalid file descriptor - throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET); + throw new ZeroTier.Sockets.SocketException((int)Constants.ERR_SOCKET); } if (_isListening == false) { throw new InvalidOperationException("Socket is not in a listening state. Call Listen() first"); @@ -281,7 +287,7 @@ namespace ZeroTier int err = zts_accept(_fd, remoteAddrPtr, addrlenPtr); if (err < 0) { - throw new ZeroTier.SocketException(err, ZeroTier.Node.ErrNo); + throw new ZeroTier.Sockets.SocketException(err, ZeroTier.Core.Node.ErrNo); } in4 = (zts_sockaddr_in)Marshal.PtrToStructure(remoteAddrPtr, typeof(zts_sockaddr_in)); // Convert sockaddr contents to IPEndPoint @@ -290,6 +296,7 @@ namespace ZeroTier // Create new socket by providing file descriptor returned from zts_accept call. Socket clientSocket = new Socket( err, _socketFamily, _socketType, _socketProtocol, _localEndPoint, clientEndPoint); + Marshal.FreeHGlobal(remoteAddrPtr); return clientSocket; } @@ -323,20 +330,6 @@ namespace ZeroTier _isClosed = true; } - public EndPoint RemoteEndPoint - { - get { - return _remoteEndPoint; - } - } - - public EndPoint LocalEndPoint - { - get { - return _localEndPoint; - } - } - public bool Blocking { get { @@ -348,18 +341,16 @@ namespace ZeroTier } int opts = 0; if ((opts = zts_fcntl(_fd, (int)(ZeroTier.Constants.F_GETFL), 0)) < 0) { - throw new ZeroTier.SocketException(opts, ZeroTier.Node.ErrNo); + throw new ZeroTier.Sockets.SocketException(opts, ZeroTier.Core.Node.ErrNo); } - Console.WriteLine("before.opts={0}", opts); if (value) { // Blocking opts = opts & (~(ZeroTier.Constants.O_NONBLOCK)); } if (!value) { // Non-Blocking opts = opts | (int)(ZeroTier.Constants.O_NONBLOCK); } - Console.WriteLine("after.opts={0}", opts); if ((opts = zts_fcntl(_fd, ZeroTier.Constants.F_SETFL, (int)opts)) < 0) { - throw new ZeroTier.SocketException(opts, ZeroTier.Node.ErrNo); + throw new ZeroTier.Sockets.SocketException(opts, ZeroTier.Core.Node.ErrNo); } _isBlocking = value; } @@ -379,7 +370,8 @@ namespace ZeroTier poll_set.events = (short)((byte)ZeroTier.Constants.POLLOUT); } if (mode == SelectMode.SelectError) { - poll_set.events = (short)((byte)ZeroTier.Constants.POLLERR | (byte)ZeroTier.Constants.POLLNVAL); + poll_set.events = (short)((byte)ZeroTier.Constants.POLLERR | + (byte)ZeroTier.Constants.POLLNVAL); } IntPtr poll_fd_ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(zts_pollfd))); Marshal.StructureToPtr(poll_set, poll_fd_ptr, false); @@ -387,20 +379,23 @@ namespace ZeroTier int timeout_ms = (microSeconds / 1000); uint numfds = 1; if ((result = zts_poll(poll_fd_ptr, numfds, timeout_ms)) < 0) { - throw new ZeroTier.SocketException(result, ZeroTier.Node.ErrNo); + throw new ZeroTier.Sockets.SocketException(result, ZeroTier.Core.Node.ErrNo); } poll_set = (zts_pollfd)Marshal.PtrToStructure(poll_fd_ptr, typeof(zts_pollfd)); - if (result == 0) { return false; } // No events - if (mode == SelectMode.SelectRead) { - return ((byte)poll_set.revents & (byte)ZeroTier.Constants.POLLIN) != 0; + if (result != 0) { + if (mode == SelectMode.SelectRead) { + result = Convert.ToInt32(((byte)poll_set.revents & (byte)ZeroTier.Constants.POLLIN) != 0); + } + if (mode == SelectMode.SelectWrite) { + result = Convert.ToInt32(((byte)poll_set.revents & (byte)ZeroTier.Constants.POLLOUT) != 0); + } + if (mode == SelectMode.SelectError) { + result = Convert.ToInt32(((poll_set.revents & (byte)ZeroTier.Constants.POLLERR) != 0) || + ((poll_set.revents & (byte)ZeroTier.Constants.POLLNVAL) != 0)); + } } - if (mode == SelectMode.SelectWrite) { - return ((byte)poll_set.revents & (byte)ZeroTier.Constants.POLLOUT) != 0; - } - if (mode == SelectMode.SelectError) { - return ((poll_set.revents & (byte)ZeroTier.Constants.POLLERR) != 0) || ((poll_set.revents & (byte)ZeroTier.Constants.POLLNVAL) != 0); - } - return false; + Marshal.FreeHGlobal(poll_fd_ptr); + return result > 0; } public Int32 Send(Byte[] buffer) @@ -409,7 +404,7 @@ namespace ZeroTier throw new ObjectDisposedException("Socket has been closed"); } if (_fd < 0) { - throw new ZeroTier.SocketException((int)ZeroTier.Constants.ERR_SOCKET); + throw new ZeroTier.Sockets.SocketException((int)ZeroTier.Constants.ERR_SOCKET); } if (buffer == null) { throw new ArgumentNullException("buffer"); @@ -425,7 +420,7 @@ namespace ZeroTier throw new ObjectDisposedException("Socket has been closed"); } if (_fd < 0) { - throw new ZeroTier.SocketException((int)ZeroTier.Constants.ERR_SOCKET); + throw new ZeroTier.Sockets.SocketException((int)ZeroTier.Constants.ERR_SOCKET); } if (buffer == null) { throw new ArgumentNullException("buffer"); @@ -435,6 +430,87 @@ namespace ZeroTier return zts_recv(_fd, bufferPtr, (uint)Buffer.ByteLength(buffer), (int)flags); } + private void _set_timeout(int timeout_ms, int optname) + { + zts_timeval tv = new zts_timeval(); + // Convert milliseconds to timeval struct + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + IntPtr tv_ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(zts_timeval))); + Marshal.StructureToPtr(tv, tv_ptr, false); + ushort option_size = (ushort)Marshal.SizeOf(typeof(zts_sockaddr_in)); + int err = 0; + if ((err = zts_setsockopt(_fd, ZeroTier.Constants.SOL_SOCKET, + ZeroTier.Constants.SO_RCVTIMEO, tv_ptr, option_size)) < 0) { + throw new ZeroTier.Sockets.SocketException(err, ZeroTier.Core.Node.ErrNo); + } + Marshal.FreeHGlobal(tv_ptr); + } + + private int _get_timeout(int optname) + { + zts_timeval tv = new zts_timeval(); + IntPtr tv_ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(zts_timeval))); + Marshal.StructureToPtr(tv, tv_ptr, false); + ushort optlen = (ushort)Marshal.SizeOf(typeof(zts_timeval)); + GCHandle optlen_gc_handle = GCHandle.Alloc(optlen, GCHandleType.Pinned); + IntPtr optlen_ptr = optlen_gc_handle.AddrOfPinnedObject(); + int err = 0; + if ((err = zts_getsockopt(_fd, ZeroTier.Constants.SOL_SOCKET, + ZeroTier.Constants.SO_RCVTIMEO, tv_ptr, optlen_ptr)) < 0) { + throw new ZeroTier.Sockets.SocketException(err, ZeroTier.Core.Node.ErrNo); + } + tv = (zts_timeval)Marshal.PtrToStructure(tv_ptr, typeof(zts_timeval)); + optlen_gc_handle.Free(); + Marshal.FreeHGlobal(tv_ptr); + // Convert timeval struct to milliseconds + return (int)((tv.tv_sec * 1000) + (tv.tv_usec / 1000)); + } + + public int ReceiveTimeout + { + get { return _get_timeout(ZeroTier.Constants.SO_RCVTIMEO); } + set { _set_timeout(value, ZeroTier.Constants.SO_RCVTIMEO); } + } + + public int SendTimeout + { + get { return _get_timeout(ZeroTier.Constants.SO_SNDTIMEO); } + set { _set_timeout(value, ZeroTier.Constants.SO_SNDTIMEO); } + } + + /* TODO + public int ReceiveBufferSize { get; set; } + + public int SendBufferSize { get; set; } + + public short Ttl { get; set; } + + public LingerOption LingerState { get; set; } + + public bool NoDelay { get; set; } + */ + + public bool Connected { get { return _isConnected; } } + + public bool IsBound { get { return _isBound; } } + + public AddressFamily AddressFamily { get { return _socketFamily; } } + + public SocketType SocketType { get { return _socketType; } } + + public ProtocolType ProtocolType { get { return _socketProtocol; } } + + /* .NET has moved to OSSupportsIPv* but libzt isn't an OS so we keep this old convention */ + public static bool SupportsIPv4 { get { return true; } } + + /* .NET has moved to OSSupportsIPv* but libzt isn't an OS so we keep this old convention */ + public static bool SupportsIPv6 { get { return true; } } + + public EndPoint RemoteEndPoint { get { return _remoteEndPoint; } } + + public EndPoint LocalEndPoint { get { return _localEndPoint; } } + /* Structures and functions used internally to communicate with lower-level C API defined in include/ZeroTierSockets.h */ @@ -587,5 +663,12 @@ namespace ZeroTier public short events; public short revents; } + + [StructLayout(LayoutKind.Sequential)] + struct zts_timeval + { + public long tv_sec; + public long tv_usec; + } } } diff --git a/src/bindings/csharp/SocketException.cs b/src/bindings/csharp/SocketException.cs index 40dfc5f..3a15568 100644 --- a/src/bindings/csharp/SocketException.cs +++ b/src/bindings/csharp/SocketException.cs @@ -13,7 +13,7 @@ using System; -namespace ZeroTier +namespace ZeroTier.Sockets { /// Exception class for ZeroTier service and low-level socket errors public class SocketException : Exception diff --git a/src/bindings/csharp/zt_wrap.cxx b/src/bindings/csharp/zt_wrap.cxx index 4d88a87..1e93c3d 100644 --- a/src/bindings/csharp/zt_wrap.cxx +++ b/src/bindings/csharp/zt_wrap.cxx @@ -587,7 +587,7 @@ SWIGEXPORT void SWIGSTDCALL CSharp_zts_delay_ms(long jarg1) { zts_delay_ms(arg1); } - +/* SWIGEXPORT int SWIGSTDCALL CSharp_zts_get_all_stats(void * jarg1) { int jresult ; zts_stats *arg1 = (zts_stats *) 0 ; @@ -612,7 +612,7 @@ SWIGEXPORT int SWIGSTDCALL CSharp_zts_get_protocol_stats(int jarg1, void * jarg2 jresult = result; return jresult; } - +*/ SWIGEXPORT int SWIGSTDCALL CSharp_zts_socket(int jarg1, int jarg2, int jarg3) { int jresult ; From 1afc8690d5f08d67fa18309cba90980dfb668a8c Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 16 Feb 2021 00:21:51 -0800 Subject: [PATCH 05/25] Move Apple framework projects into new pkg/ directory --- {ports => pkg}/apple/build-xcframework.sh | 0 {ports => pkg}/apple/module.modulemap | 0 {ports => pkg}/apple/zt.xcodeproj/project.pbxproj | 0 .../project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist | 0 .../project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings | 0 .../evanolcott.xcuserdatad/WorkspaceSettings.xcsettings | 0 .../apple/zt.xcodeproj/xcshareddata/xcschemes/zt.xcscheme | 0 .../evanolcott.xcuserdatad/xcschemes/xcschememanagement.plist | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename {ports => pkg}/apple/build-xcframework.sh (100%) rename {ports => pkg}/apple/module.modulemap (100%) rename {ports => pkg}/apple/zt.xcodeproj/project.pbxproj (100%) rename {ports => pkg}/apple/zt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {ports => pkg}/apple/zt.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {ports => pkg}/apple/zt.xcodeproj/project.xcworkspace/xcuserdata/evanolcott.xcuserdatad/WorkspaceSettings.xcsettings (100%) rename {ports => pkg}/apple/zt.xcodeproj/xcshareddata/xcschemes/zt.xcscheme (100%) rename {ports => pkg}/apple/zt.xcodeproj/xcuserdata/evanolcott.xcuserdatad/xcschemes/xcschememanagement.plist (100%) diff --git a/ports/apple/build-xcframework.sh b/pkg/apple/build-xcframework.sh similarity index 100% rename from ports/apple/build-xcframework.sh rename to pkg/apple/build-xcframework.sh diff --git a/ports/apple/module.modulemap b/pkg/apple/module.modulemap similarity index 100% rename from ports/apple/module.modulemap rename to pkg/apple/module.modulemap diff --git a/ports/apple/zt.xcodeproj/project.pbxproj b/pkg/apple/zt.xcodeproj/project.pbxproj similarity index 100% rename from ports/apple/zt.xcodeproj/project.pbxproj rename to pkg/apple/zt.xcodeproj/project.pbxproj diff --git a/ports/apple/zt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/pkg/apple/zt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from ports/apple/zt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to pkg/apple/zt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/ports/apple/zt.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/pkg/apple/zt.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from ports/apple/zt.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to pkg/apple/zt.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/ports/apple/zt.xcodeproj/project.xcworkspace/xcuserdata/evanolcott.xcuserdatad/WorkspaceSettings.xcsettings b/pkg/apple/zt.xcodeproj/project.xcworkspace/xcuserdata/evanolcott.xcuserdatad/WorkspaceSettings.xcsettings similarity index 100% rename from ports/apple/zt.xcodeproj/project.xcworkspace/xcuserdata/evanolcott.xcuserdatad/WorkspaceSettings.xcsettings rename to pkg/apple/zt.xcodeproj/project.xcworkspace/xcuserdata/evanolcott.xcuserdatad/WorkspaceSettings.xcsettings diff --git a/ports/apple/zt.xcodeproj/xcshareddata/xcschemes/zt.xcscheme b/pkg/apple/zt.xcodeproj/xcshareddata/xcschemes/zt.xcscheme similarity index 100% rename from ports/apple/zt.xcodeproj/xcshareddata/xcschemes/zt.xcscheme rename to pkg/apple/zt.xcodeproj/xcshareddata/xcschemes/zt.xcscheme diff --git a/ports/apple/zt.xcodeproj/xcuserdata/evanolcott.xcuserdatad/xcschemes/xcschememanagement.plist b/pkg/apple/zt.xcodeproj/xcuserdata/evanolcott.xcuserdatad/xcschemes/xcschememanagement.plist similarity index 100% rename from ports/apple/zt.xcodeproj/xcuserdata/evanolcott.xcuserdatad/xcschemes/xcschememanagement.plist rename to pkg/apple/zt.xcodeproj/xcuserdata/evanolcott.xcuserdatad/xcschemes/xcschememanagement.plist From 20fba7312b87b9e6fa60d83ad3089769caa98fa2 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 16 Feb 2021 00:23:22 -0800 Subject: [PATCH 06/25] Remove errant gradle files --- gradle/wrapper/gradle-wrapper.properties | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 gradle/wrapper/gradle-wrapper.properties diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 19869d6..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip From 2648a67331bdca4cc6f3b068ee882b64db0df234 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 16 Feb 2021 00:24:38 -0800 Subject: [PATCH 07/25] Rename selftest --- test/{error.cpp => selftest.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{error.cpp => selftest.cpp} (100%) diff --git a/test/error.cpp b/test/selftest.cpp similarity index 100% rename from test/error.cpp rename to test/selftest.cpp From a055ee8012c3dd75cdfc69f1ddbe054734f5cf45 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 16 Feb 2021 00:34:01 -0800 Subject: [PATCH 08/25] Move C API documentation to include directory --- include/README.md | 429 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 426 insertions(+), 3 deletions(-) diff --git a/include/README.md b/include/README.md index f2318b7..fe36f9b 100644 --- a/include/README.md +++ b/include/README.md @@ -1,4 +1,427 @@ -ZeroTier Socket API -====== +C API Documentation +===== +This is the externally facing plain C API. All language bindings interface with this. It provides a uniform socket interface across all supported platforms. + +--- + +### Table of Contents ### + + - [Starting ZeroTier](#starting-zerotier) + - [Joining a Network](#joining-a-network) + - [Connecting to peers](#connecting-to-peers) + - [Event Handling](#event-handling) + - [Node Events](#node-events) + - [Network Events](#network-events) + - [Peer Events](#peer-events) + - [Address Events](#address-events) + - [Error Handling](#error-handling) + - [Common pitfalls](#common-pitfalls) + - [API compatibility with host OS](#api-compatibility-with-host-os) + - [Thread Model](#thread-model) + - [Debugging](#debugging) + +# Starting ZeroTier + +The next few sections explain how to use the network control interface portion of the API. These functions are non-blocking and will return an error code specified in the [Error Handling](#error-handling) section and will result in the generation of callback events detailed in the [Event Handling](#event-handling) section. It is your responsibility to handle these events. + +To start the service, simply call: + +```c +zts_start(const char *path, void (*callback)(void *), uint16_t port); +zts_callback_msg*), int port)` +``` + +At this stage, if a cryptographic identity for this node does not already exist on your local storage medium, it will generate a new one and store it, the node's address (commonly referred to as `nodeId`) will be derived from this identity and will be presented to you upon receiving the `ZTS_EVENT_NODE_ONLINE` shown below. The first argument `path` is a path where you will direct ZeroTier to store its automatically-generated cryptographic identity files (`identity.public` and `identity.secret`), these files are your keys to communicating on the network. Keep them safe and keep them unique. If any two nodes are online using the same identities you will have a bad time. The second argument `userCallbackFunc` is a function that you specify to handle all generated events for the life of your program, see below: + +```c +#include "ZeroTierSockets.h" + +... + +bool networkReady = false; + +void on_zts_event(struct zts_callback_msg *msg) +{ + if (msg->eventCode == ZTS_EVENT_NODE_ONLINE) { + printf("ZTS_EVENT_NODE_ONLINE, nodeId=%llx\n", msg->node->address); + networkReady = true; + } + ... +} + +int main() +{ + zts_start("configPath", &on_zts_event, 9994); + uint64_t nwid = 0x0123456789abcdef; + while (!networkReady) { sleep(1); } + zts_join(nwid); + int fd = zts_socket(ZTS_AF_INET, ZTS_SOCK_STREAM, 0); + ... + return 0; +} + +``` + +For more complete examples see `./examples/` + +
+ +After calling `zts_start()` you will receive one or more events specified in the [Node Events](#node-events) section. After receiving `ZTS_EVENT_NODE_ONLINE` you will be allowed to join or leave networks. You must authorize the node ID provided by the this callback event to join your network. This can be done manually or via our [Web API](https://my.zerotier.com/help/api). Note however that if you are using an Ad-hoc network, it has no controller and therefore requires no authorization. + +At the end of your program or when no more network activity is anticipated, the user application can shut down the service with `zts_stop()`. However, it is safe to leave the service running in the background indefinitely as it doesn't consume much memory or CPU while at idle. `zts_stop()` is a non-blocking call and will itself issue a series of events indicating that various aspects of the ZeroTier service have successfully shut down. + +It is worth noting that while `zts_stop()` will stop the service, the user-space network stack will continue operating in a headless hibernation mode. This is intended behavior due to the fact that the network stack we've chosen doesn't currently support the notion of shutdown since it was initially designed for embedded applications that are simply switched off. If you do need a way to shut everything down and free all resources you can call `zts_free()`, but please note that calling this function will prevent all subsequent `zts_start()` calls from succeeding and will require a full application restart if you want to run the service again. The events `ZTS_EVENT_NODE_ONLINE` and `ZTS_EVENT_NODE_OFFLINE` can be seen periodically throughout the lifetime of your application depending on the reliability of your underlying network link, these events are lagging indicators and are typically only triggered every thirty (30) seconds. + +Lastly, the function `zts_restart()` is provided as a way to restart the ZeroTier service along with all of its virtual interfaces. The network stack will remain online and undisturbed during this call. Note that this call will temporarily block until the service has fully shut down, then will return and you may then watch for the appropriate startup callbacks mentioned above. + +
+ +# Joining a network + - #### [Back to Table of Contents](#table-of-contents) + +Joining a ZeroTier virtual network is as easy as calling `zts_join(uint64_t networkId)`. Similarly there is a `zts_leave(uint64_t networkId)`. Note that `zts_start()` must be called and a `ZTS_EVENT_NODE_ONLINE` event must have been received before these calls will succeed. After calling `zts_join()` any one of the events detailed in the [Network Events](#network-events) section may be generated. + +# Connecting to peers + - #### [Back to Table of Contents](#table-of-contents) + +Creating a standard socket connection generally works the same as it would using an ordinary socket interface, however with ZeroTier there is a subtle difference in how connections are established which may cause confusion. Since ZeroTier employs transport-triggered link provisioning a direct connection between peers will not exist until contact has been attempted by at least one peer. During this time before a direct link is available traffic will be handled via our free relay service. The provisioning of this direct link usually only takes a couple of seconds but it is important to understand that if you attempt something like s `zts_connect(...)` call during this time it may fail due to packet loss. Therefore it is advised to repeatedly call `zts_connect(...)` until it succeeds and to wait to send additional traffic until `ZTS_EVENT_PEER_DIRECT` has been received for the peer you are attempting to communicate with. All of the above is optional, but it will improve your experience. + +`tl;dr: Try a few times and wait a few seconds` + +As a mitigation for the above behavior, ZeroTier will by default cache details about how to contact a peer in the `peers.d` subdirectory of the config path you passed to `zts_start(...)`. In scenarios where paths do not often change, this can almost completely eliminate the issue and will make connections nearly instantaneous. If however you do not wish to cache these details you can disable it via `zts_set_peer_caching(false)`. + +
+ +# Event handling + - #### [Back to Table of Contents](#table-of-contents) + +As mentioned in previous sections, the control API works by use of non-blocking calls and the generation of a few dozen different event types. Depending on the type of event there may be additional contextual information attached to the `zts_callback_msg` object that you can use. This contextual information will be housed in one of the following structures which are defined in `include/ZeroTierSockets.h`: + +```c +struct zts_callback_msg +{ + int eventCode; + struct zts_node_details *node; + struct zts_network_details *network; + struct zts_netif_details *netif; + struct zts_virtual_network_route *route; + struct zts_peer_details *peer; + struct zts_addr_details *addr; +}; +``` + +Here's an example of a callback function: + +```c +void on_zts_event(struct zts_callback_msg *msg) +{ + if (msg->eventCode == ZTS_EVENT_NODE_ONLINE) { + printf("ZTS_EVENT_NODE_ONLINE, node=%llx\n", msg->node->address); + // You can join networks now! + } +} +``` + +In this callback function you can perform additional non-blocking API calls or other work. While not returning control to the service isn't forbidden (the event messages are generated by a separate thread) it is recommended that you return control as soon as possible as not returning will prevent the user application from receiving additional callback event messages which may be time-sensitive. + +
+ +A typical ordering of messages may look like the following: + +```c +... +ZTS_EVENT_NODE_ONLINE // Your node is ready to be used. +ZTS_EVENT_ADDR_ADDED_IP4 // Your node received an IP address assignment on a given network. +ZTS_EVENT_NETWORK_UPDATE // Something about a network changed. +ZTS_EVENT_NETWORK_READY_IP4 // Your node has joined a network, has an address, and can send/receive traffic. +ZTS_EVENT_PEER_RELAY // A peer was discovered but no direct path exists (yet.) +... +ZTS_EVENT_PEER_DIRECT // One or more direct paths to a peer were discovered. +``` + +## Node Events + - #### [Back to Table of Contents](#table-of-contents) + +Accessible via `msg->node` as a `zts_node_details` object, this message type will contain information about the status of your node. *Possible values of `msg->eventCode`:* + +```c +ZTS_EVENT_NODE_OFFLINE // Your node is offline. +ZTS_EVENT_NODE_ONLINE // Your node is online and ready to communicate! +ZTS_EVENT_NODE_DOWN // The node is down (for any reason.) +ZTS_EVENT_NODE_IDENTITY_COLLISION // There is another node with the same identity causing a conflict. +ZTS_EVENT_NODE_UNRECOVERABLE_ERROR // Something went wrong internally. +ZTS_EVENT_NODE_NORMAL_TERMINATION // Your node has terminated. +``` + +*Example contents of `msg->node`:* + +``` +id : f746d550dd +version : 1.4.6 +primaryPort : 9995 +secondaryPort : 0 +``` + +## Network Events + - #### [Back to Table of Contents](#table-of-contents) + +Accessible via `msg->network` as a `zts_network_details` object, this message type will contain information about the status of a particular network your node has joined. *Possible values of `msg->eventCode`:* + +```c +ZTS_EVENT_NETWORK_NOT_FOUND // The network does not exist. The provided networkID may be incorrect. +ZTS_EVENT_NETWORK_CLIENT_TOO_OLD // This client is too old. +ZTS_EVENT_NETWORK_REQ_CONFIG // Waiting for network config, this might take a few seconds. +ZTS_EVENT_NETWORK_OK // Node successfully joined. +ZTS_EVENT_NETWORK_ACCESS_DENIED // The network is private. Your node requires authorization. +ZTS_EVENT_NETWORK_READY_IP4 // Your node successfully received an IPv4 address. +ZTS_EVENT_NETWORK_READY_IP6 // Your node successfully received an IPv6 address. +ZTS_EVENT_NETWORK_DOWN // For some reason the network is no longer available. +ZTS_EVENT_NETWORK_UPDATE // The network's config has changed: mtu, name, managed route, etc. +``` + +*Example contents of `msg->network`:* + +``` +nwid : 8bd712bf36bdae5f +mac : ae53fa031fcf +name : cranky_hayes +type : 0 +mtu : 2800 +dhcp : 0 +bridge : 0 +broadcastEnabled : 1 +portError : 0 +netconfRevision : 34 +routeCount : 1 +multicastSubscriptionCount : 1 +- mac=ffffffffffff, adi=ac1b2561 +addresses: +- FC5D:69B6:E0F7:46D5:50DD::1 +- 172.27.37.97 +routes: +- target : 172.27.0.0 +- via : 0.0.0.0 + - flags : 0 + - metric : 0 +``` + +
+ +## Peer Events + - #### [Back to Table of Contents](#table-of-contents) + +Accessible via `msg->peer` as a `zts_peer_details` object, this message type will contain information about a peer that was discovered by your node. These events are triggered when the reachability status of a peer has changed. *Possible values of `msg->eventCode`:* + +```c +ZTS_EVENT_PEER_DIRECT // At least one direct path to this peer is known. +ZTS_EVENT_PEER_RELAY // No direct path to this peer is known. It will be relayed, (high packet loss and jitter.) +ZTS_EVENT_PEER_UNREACHABLE // Peer is not reachable by any means. +ZTS_EVENT_PEER_PATH_DISCOVERED // A new direct path to this peer has been discovered. +ZTS_EVENT_PEER_PATH_DEAD // A direct path to this peer has expired. +``` + +*Example contents of `msg->peer`:* + +``` +peer : a747d5502d +role : 0 +latency : 4 +version : 1.4.6 +pathCount : 2 + - 172.27.37.97 + - F75D:69B6:E0C7:47D5:51DB::1 +``` + +## Address Events + - #### [Back to Table of Contents](#table-of-contents) + +Accessible via `msg->addr` as a `zts_addr_details` object, this message type will contain information about addresses assign to your node on a particular network. The information contained in these events is also available via `ZTS_EVENT_NETWORK_UPDATE` events. *Possible values of `msg->eventCode`:* + +```c +ZTS_EVENT_ADDR_ADDED_IP4 // A new IPv4 address was assigned to your node on the indicated network. +ZTS_EVENT_ADDR_REMOVED_IP4 // An IPv4 address assignment to your node was removed on the indicated network. +ZTS_EVENT_ADDR_ADDED_IP6 // A new IPv6 address was assigned to your node on the indicated network. +ZTS_EVENT_ADDR_REMOVED_IP6 // An IPv6 address assignment to your node was removed on the indicated network. +``` + +*Example contents of `msg->addr`:* + +``` +nwid : a747d5502d +addr : 172.27.37.97 +``` + +
+ +# Error handling + - #### [Back to Table of Contents](#table-of-contents) + +Calling a `zts_*` function will result in one of the following return codes. Only when `ZTS_ERR` is returned will `zts_errno` be set. Its values closely mirror those used in standard socket interfaces and are defined in `include/ZeroTierSockets.h`. + +```c +ZTS_ERR_OK // No error +ZTS_ERR_SOCKET // Socket error (see zts_errno for more information) +ZTS_ERR_SERVICE // General ZeroTier internal error. Maybe you called something out of order? +ZTS_ERR_ARG // An argument provided is invalid. +ZTS_ERR_NO_RESULT // Call succeeded but no result was available. Not necessarily an error. +ZTS_ERR_GENERAL // General internal failure. Consider filing a bug report. +``` + +*NOTE: For Android/Java (or similar) which use JNI, the socket API's error codes are negative values encoded in the return values of function calls* +*NOTE: For protocol-level errors (such as dropped packets) or internal network stack errors, see the section `Statistics`* + +
+ +# Common pitfalls + - #### [Back to Table of Contents](#table-of-contents) + + - If you have started a node but have not received a `ZTS_EVENT_NODE_ONLINE`: + - You may need to view our [Router Config Tips](https://zerotier.atlassian.net/wiki/spaces/SD/pages/6815768/Router+Configuration+Tips) knowledgebase article. Sometimes this is due to firewall/NAT settings. + + - If you have received a `ZTS_EVENT_NODE_ONLINE` event and attempted to join a network but do not see your node ID in the network panel on [my.zerotier.com](my.zerotier.com) after some time: + - You may have typed in your network ID incorrectly. + - Used an improper integer representation for your network ID (e.g. `int` instead of `uint64_t`). + + - If you are unable to reliably connect to peers: + - You should first read the section on [Connecting and communicating with peers](#connecting-and-communicating-with-peers). + - If the previous step doesn't help move onto our knowledgebase article [Router Config Tips](https://zerotier.atlassian.net/wiki/spaces/SD/pages/6815768/Router+Configuration+Tips). Sometimes this can be a transport-triggered link issue, and sometimes it can be a firewall/NAT issue. + + - API calls seem to fail in nonsensical ways and you're tearing your hair out: + - Be sure to read and understand the [API compatibility with host OS](#api-compatibility-with-host-os) section. + - See the [Debugging](#debugging) section for more advice. + +
+ +# API compatibility with host OS + - #### [Back to Table of Contents](#table-of-contents) + +Since libzt re-implements a socket interface likely very similar to your host OS's own interface it may be tempting to mix and match host OS structures and functions with those of libzt. This may work on occasion, but you are tempting fate. Here are a few important guidelines: + +If you are calling a `zts_*` function, use the appropriate `ZTS_*` constants: +```c +zts_socket(ZTS_AF_INET6, ZTS_SOCK_DGRAM, 0); (CORRECT) +zts_socket(AF_INET6, SOCK_DGRAM, 0); (INCORRECT) +``` + +If you are calling a `zts_*` function, use the appropriate `zts_*` structure: +```c +struct zts_sockaddr_in in4; <------ Note the zts_ prefix + ... +zts_bind(fd, (struct sockaddr *)&in4, sizeof(struct zts_sockaddr_in)) < 0) +``` + +If you are calling a host OS function, use your host OS's constants (and structures!): + +```c +inet_ntop(AF_INET6, &(in6->sin6_addr), ...); (CORRECT) +inet_ntop(ZTS_AF_INET6, &(in6->sin6_addr), ...); (INCORRECT) +zts_inet_ntop(ZTS_AF_INET6, &(in6->sin6_addr), ...); (CORRECT) +``` + +If you are calling a host OS function but passing a `zts_*` structure, this can work sometimes but you should take care to pass the correct host OS constants: +```c +struct zts_sockaddr_in6 in6; + ... +inet_ntop(AF_INET6, &(in6->sin6_addr), dstStr, INET6_ADDRSTRLEN); +``` + +
+ +# Thread model + - #### [Back to Table of Contents](#table-of-contents) + +Both the **socket** and **control** interfaces are thread-safe but are implemented differently. The socket interface is implemented using a relatively performant core locking mechanism in lwIP. This can be disabled if you know what you're doing. The control interface is implemented by a single coarse-grained lock. This lock is not a performance bottleneck since it only applies to functions that manipulate the ZeroTier service and are called seldomly. Callback events are generated by a separate thread and are independent from the rest of the API's internal locking mechanism. Not returning from a callback event won't impact the rest of the API but it will prevent your application from receiving future events so it is in your application's best interest to perform as little work as possible in the callback function and promptly return control back to ZeroTier. + +*Note: Internally, `libzt` will spawn a number of threads for various purposes: a thread for the core service, a thread for the network stack, a low priority thread to process callback events, and a thread for each network joined. The vast majority of work is performed by the core service and stack threads.* + +
+ +# Debugging + - #### [Back to Table of Contents](#table-of-contents) + +If you're experiencing odd behavior or something that looks like a bug I would suggest first reading and understanding the following sections: + +* [Common pitfalls](#common-pitfalls) +* [API compatibility with host OS](#api-compatibility-with-host-os) +* [Thread model](#thread-model) + +If the information in those sections hasn't helped, there are a couple of ways to get debug traces out of various parts of the library. + +1) Build the library in debug mode with `make host_debug`. This will prevent the stripping of debug symbols from the library and will enable basic output traces from libzt. + +2) If you believe your problem is in the network stack you can manually enable debug traces for individual modules in `src/lwipopts.h`. Toggle the `*_DEBUG` types from `LWIP_DBG_OFF` to `LWIP_DBG_ON`. And then rebuild. This will come with a significant performance cost. + +3) Enabling network stack statistics. This is useful if you want to monitor the stack's receipt and handling of traffic as well as internal things like memory allocations and cache hits. Protocol and service statistics are available in debug builds of `libzt`. These statistics are detailed fully in the section of `include/ZeroTierSockets.h` that is guarded by `LWIP_STATS`. + + ```c + struct zts_stats_proto stats; + if (zts_get_protocol_stats(ZTS_STATS_PROTOCOL_ICMP, &stats) == ZTS_ERR_OK) { + printf("icmp.recv=%d\n", stats.recv); // Count of received pings + } + if (zts_get_protocol_stats(ZTS_STATS_PROTOCOL_TCP, &stats) == ZTS_ERR_OK) { + printf("tcp.drop=%d\n", stats.drop); // Count of dropped TCP packets + } + ``` + +4) There are a series of additional events which can signal whether the network stack or its virtual network interfaces have been set up properly. See `ZTS_EVENT_STACK_*` and `ZTS_EVENT_NETIF_*`. + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -This is the externally facing plain C API. It provides a platform-agnostic ZeroTier-based socket interface. From 842059aeef5175e826caa2ca32407fd9f97d1d64 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 16 Feb 2021 22:31:34 -0800 Subject: [PATCH 09/25] Remove incomplete rust wrapper files --- examples/rust/binding-example/Cargo.lock | 5 ----- examples/rust/binding-example/Cargo.toml | 10 ---------- examples/rust/binding-example/build.rs | 4 ---- examples/rust/binding-example/src/main.rs | 10 ---------- examples/rust/program.c | 6 ------ examples/rust/program.h | 1 - examples/rust/program.rs | 5 ----- 7 files changed, 41 deletions(-) delete mode 100644 examples/rust/binding-example/Cargo.lock delete mode 100644 examples/rust/binding-example/Cargo.toml delete mode 100644 examples/rust/binding-example/build.rs delete mode 100644 examples/rust/binding-example/src/main.rs delete mode 100644 examples/rust/program.c delete mode 100644 examples/rust/program.h delete mode 100644 examples/rust/program.rs diff --git a/examples/rust/binding-example/Cargo.lock b/examples/rust/binding-example/Cargo.lock deleted file mode 100644 index 7c0a5dd..0000000 --- a/examples/rust/binding-example/Cargo.lock +++ /dev/null @@ -1,5 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "binding-example" -version = "0.1.0" diff --git a/examples/rust/binding-example/Cargo.toml b/examples/rust/binding-example/Cargo.toml deleted file mode 100644 index fe94196..0000000 --- a/examples/rust/binding-example/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "binding-example" -version = "0.1.0" -authors = ["Joseph Henry "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -#rustc-link-search = ["../../../lib/debug/macos-x86_64/"] - -[dependencies] diff --git a/examples/rust/binding-example/build.rs b/examples/rust/binding-example/build.rs deleted file mode 100644 index bf44483..0000000 --- a/examples/rust/binding-example/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rustc-flags=-l dylib=c++"); - //println!("cargo:rustc-link-search=."); -} \ No newline at end of file diff --git a/examples/rust/binding-example/src/main.rs b/examples/rust/binding-example/src/main.rs deleted file mode 100644 index cd9f0fd..0000000 --- a/examples/rust/binding-example/src/main.rs +++ /dev/null @@ -1,10 +0,0 @@ - -#[link(name = "libzt", kind = "dylib")] -extern { - fn zts_socket(address_family: i32) -> i32; -} - -fn main() { - let x = unsafe { zts_socket(100) }; - println!("zts_socket() = {}", x); -} diff --git a/examples/rust/program.c b/examples/rust/program.c deleted file mode 100644 index 816f383..0000000 --- a/examples/rust/program.c +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int zts_socket(int address_family) -{ - return -777; -} diff --git a/examples/rust/program.h b/examples/rust/program.h deleted file mode 100644 index 23847a0..0000000 --- a/examples/rust/program.h +++ /dev/null @@ -1 +0,0 @@ -int zts_socket(int address_family); diff --git a/examples/rust/program.rs b/examples/rust/program.rs deleted file mode 100644 index 169c9dd..0000000 --- a/examples/rust/program.rs +++ /dev/null @@ -1,5 +0,0 @@ -/* automatically generated by rust-bindgen 0.56.0 */ - -extern "C" { - pub fn zts_socket(address_family: ::std::os::raw::c_int) -> ::std::os::raw::c_int; -} From 4fd619b7cb1772a6acfbdcf2d10a81eae048e3d6 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 16 Feb 2021 22:32:17 -0800 Subject: [PATCH 10/25] Remove obsolete Java ports directory --- ports/java/com/zerotier/libzt/README | 1 - 1 file changed, 1 deletion(-) delete mode 100644 ports/java/com/zerotier/libzt/README diff --git a/ports/java/com/zerotier/libzt/README b/ports/java/com/zerotier/libzt/README deleted file mode 100644 index 062ccae..0000000 --- a/ports/java/com/zerotier/libzt/README +++ /dev/null @@ -1 +0,0 @@ -Before build, Java sources are copied here from src/java in the root directory of this repo. From 888030c827faf36ace709eda93ccd99c08d548fb Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 23 Feb 2021 21:30:44 -0800 Subject: [PATCH 11/25] Update Java example --- examples/java/Example.java | 161 ++++++++++++++++++ examples/java/Makefile | 9 + examples/java/README.md | 26 +-- .../libzt/javasimpleexample/Main.java | 64 ------- .../MyZeroTierEventListener.java | 96 ----------- 5 files changed, 175 insertions(+), 181 deletions(-) create mode 100644 examples/java/Example.java create mode 100644 examples/java/Makefile delete mode 100644 examples/java/src/com/zerotier/libzt/javasimpleexample/Main.java delete mode 100644 examples/java/src/com/zerotier/libzt/javasimpleexample/MyZeroTierEventListener.java diff --git a/examples/java/Example.java b/examples/java/Example.java new file mode 100644 index 0000000..a5f23de --- /dev/null +++ b/examples/java/Example.java @@ -0,0 +1,161 @@ +//package com.zerotier.libzt.javasimpleexample; + +import com.zerotier.libzt.ZeroTier; +import com.zerotier.libzt.ZeroTierEventListener; +import java.math.BigInteger; + +public class Example { + + static void sleep(int ms) { + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public static void main(String[] args) { + String keyPath="."; // Default to current directory + BigInteger networkId; + Integer servicePort=0; + if (args.length != 3) { + System.err.println(" Invalid arguments"); + System.err.println(" Usage: example "); + System.exit(1); + } + keyPath = args[0]; + networkId = new BigInteger(args[1], 16); + servicePort = Integer.parseInt(args[2]); + System.out.println("networkId = " + Long.toHexString(networkId.longValue())); + System.out.println("keyPath = " + keyPath); + System.out.println("servicePort = " + servicePort); + + // + // BEGIN SETUP + // + + // Set up event listener and start service + MyZeroTierEventListener listener = new MyZeroTierEventListener(); + ZeroTier.start(keyPath, (ZeroTierEventListener)listener, servicePort); + // Wait for EVENT_NODE_ONLINE + System.out.println("waiting for node to come online..."); + while (listener.isOnline == false) { sleep(50); } + System.out.println("joining network"); + ZeroTier.join(networkId.longValue()); + // Wait for EVENT_NETWORK_READY_IP4/6 + System.out.println("waiting for network config..."); + while (listener.isNetworkReady == false) { sleep(50); } + System.out.println("joined"); + + // + // END SETUP + // + + sleep(120000); + + /** + * + * Begin using socket API after this point: + * + * ZeroTier.ZeroTierSocket, ZeroTier.ZeroTierSocketFactory, etc + * + * (or) + * + * ZeroTier.socket(), ZeroTier.connect(), etc + */ + } +} + +/** + * Example event handling + */ +class MyZeroTierEventListener implements ZeroTierEventListener { + + public boolean isNetworkReady = false; + public boolean isOnline = false; + + public void onZeroTierEvent(long id, int eventCode) { + if (eventCode == ZeroTier.EVENT_NODE_UP) { + System.out.println("EVENT_NODE_UP: nodeId=" + Long.toHexString(id)); + } + if (eventCode == ZeroTier.EVENT_NODE_ONLINE) { + // The core service is running properly and can join networks now + System.out.println("EVENT_NODE_ONLINE: nodeId=" + Long.toHexString(id)); + isOnline = true; + } + if (eventCode == ZeroTier.EVENT_NODE_OFFLINE) { + // Network does not seem to be reachable by any available strategy + System.out.println("EVENT_NODE_OFFLINE"); + } + if (eventCode == ZeroTier.EVENT_NODE_DOWN) { + // Called when the node is shutting down + System.out.println("EVENT_NODE_DOWN"); + } + if (eventCode == ZeroTier.EVENT_NODE_IDENTITY_COLLISION) { + // Another node with this identity already exists + System.out.println("EVENT_NODE_IDENTITY_COLLISION"); + } + if (eventCode == ZeroTier.EVENT_NODE_UNRECOVERABLE_ERROR) { + // Try again + System.out.println("EVENT_NODE_UNRECOVERABLE_ERROR"); + } + if (eventCode == ZeroTier.EVENT_NODE_NORMAL_TERMINATION) { + // Normal closure + System.out.println("EVENT_NODE_NORMAL_TERMINATION"); + } + if (eventCode == ZeroTier.EVENT_NETWORK_READY_IP4) { + // We have at least one assigned address and we've received a network configuration + System.out.println("ZTS_EVENT_NETWORK_READY_IP4: nwid=" + Long.toHexString(id)); + isNetworkReady = true; + } + if (eventCode == ZeroTier.EVENT_NETWORK_READY_IP6) { + // We have at least one assigned address and we've received a network configuration + System.out.println("ZTS_EVENT_NETWORK_READY_IP6: nwid=" + Long.toHexString(id)); + //isNetworkReady = true; + } + if (eventCode == ZeroTier.EVENT_NETWORK_DOWN) { + // Someone called leave(), we have no assigned addresses, or otherwise cannot use this interface + System.out.println("EVENT_NETWORK_DOWN: nwid=" + Long.toHexString(id)); + } + if (eventCode == ZeroTier.EVENT_NETWORK_REQ_CONFIG) { + // Waiting for network configuration + System.out.println("EVENT_NETWORK_REQ_CONFIG: nwid=" + Long.toHexString(id)); + } + if (eventCode == ZeroTier.EVENT_NETWORK_OK) { + // Config received and this node is authorized for this network + System.out.println("EVENT_NETWORK_OK: nwid=" + Long.toHexString(id)); + } + if (eventCode == ZeroTier.EVENT_NETWORK_ACCESS_DENIED) { + // You are not authorized to join this network + System.out.println("EVENT_NETWORK_ACCESS_DENIED: nwid=" + Long.toHexString(id)); + } + if (eventCode == ZeroTier.EVENT_NETWORK_NOT_FOUND) { + // The virtual network does not exist + System.out.println("EVENT_NETWORK_NOT_FOUND: nwid=" + Long.toHexString(id)); + } + if (eventCode == ZeroTier.EVENT_NETWORK_CLIENT_TOO_OLD) { + // The core version is too old + System.out.println("EVENT_NETWORK_CLIENT_TOO_OLD: nwid=" + Long.toHexString(id)); + } + if (eventCode == ZeroTier.EVENT_PEER_DIRECT) { + System.out.println("EVENT_PEER_DIRECT: id=" + Long.toHexString(id)); +/* + ZeroTierPeerDetails details = new ZeroTierPeerDetails(); + ZeroTier.get_peer(id, details); + System.out.println("address="+Long.toHexString(details.address)); + System.out.println("pathCount="+details.pathCount); + System.out.println("version="+details.versionMajor+"."+details.versionMinor+"."+details.versionRev); + System.out.println("latency="+details.latency); // Not relevant + System.out.println("role="+details.role); // Not relevent + // Print all known paths + for (int i=0; i. - * - * -- - * - * You can be released from the requirements of the license by purchasing - * a commercial license. Buying such a license is mandatory as soon as you - * develop commercial closed-source software that incorporates or links - * directly against ZeroTier software without disclosing the source code - * of your own application. - */ - -package com.zerotier.libzt.javasimpleexample; - -import com.zerotier.libzt.ZeroTier; - -public class Main { - - static void sleep(int ms) { - try { - Thread.sleep(ms); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - public static void main(String[] args) { - // Set up event listener and start service - MyZeroTierEventListener listener = new MyZeroTierEventListener(); - int servicePort = 9994; - ZeroTier.start("test/path", listener, servicePort); - // Wait for EVENT_NODE_ONLINE - System.out.println("waiting for node to come online..."); - while (listener.isOnline == false) { sleep(50); } - System.out.println("joining network"); - ZeroTier.join(0x0123456789abcdefL); - // Wait for EVENT_NETWORK_READY_IP4/6 - System.out.println("waiting for network config..."); - while (listener.isNetworkReady == false) { sleep(50); } - System.out.println("joined"); - - /* - - Begin using socket API after this point - Use ZeroTier.ZeroTierSocket, ZeroTier.ZeroTierSocketFactory, etc - (or) - ZeroTier.socket(), ZeroTier.connect(), etc - */ - } -} diff --git a/examples/java/src/com/zerotier/libzt/javasimpleexample/MyZeroTierEventListener.java b/examples/java/src/com/zerotier/libzt/javasimpleexample/MyZeroTierEventListener.java deleted file mode 100644 index e9b5219..0000000 --- a/examples/java/src/com/zerotier/libzt/javasimpleexample/MyZeroTierEventListener.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.zerotier.libzt.javasimpleexample; - -import com.zerotier.libzt.ZeroTier; -import com.zerotier.libzt.ZeroTierEventListener; - -public class MyZeroTierEventListener implements ZeroTierEventListener { - - public boolean isNetworkReady = false; - public boolean isOnline = false; - - public void onZeroTierEvent(long id, int eventCode) { - if (eventCode == ZeroTier.EVENT_NODE_UP) { - System.out.println("EVENT_NODE_UP: nodeId=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_NODE_ONLINE) { - // The core service is running properly and can join networks now - System.out.println("EVENT_NODE_ONLINE: nodeId=" + Long.toHexString(ZeroTier.get_node_id())); - isOnline = true; - } - if (eventCode == ZeroTier.EVENT_NODE_OFFLINE) { - // Network does not seem to be reachable by any available strategy - System.out.println("EVENT_NODE_OFFLINE"); - } - if (eventCode == ZeroTier.EVENT_NODE_DOWN) { - // Called when the node is shutting down - System.out.println("EVENT_NODE_DOWN"); - } - if (eventCode == ZeroTier.EVENT_NODE_IDENTITY_COLLISION) { - // Another node with this identity already exists - System.out.println("EVENT_NODE_IDENTITY_COLLISION"); - } - if (eventCode == ZeroTier.EVENT_NODE_UNRECOVERABLE_ERROR) { - // Try again - System.out.println("EVENT_NODE_UNRECOVERABLE_ERROR"); - } - if (eventCode == ZeroTier.EVENT_NODE_NORMAL_TERMINATION) { - // Normal closure - System.out.println("EVENT_NODE_NORMAL_TERMINATION"); - } - if (eventCode == ZeroTier.EVENT_NETWORK_READY_IP4) { - // We have at least one assigned address and we've received a network configuration - System.out.println("ZTS_EVENT_NETWORK_READY_IP4: nwid=" + Long.toHexString(id)); - if (id == 0xa09acf0233e4b070L) { - isNetworkReady = true; - } - } - if (eventCode == ZeroTier.EVENT_NETWORK_READY_IP6) { - // We have at least one assigned address and we've received a network configuration - System.out.println("ZTS_EVENT_NETWORK_READY_IP6: nwid=" + Long.toHexString(id)); - //isNetworkReady = true; - } - if (eventCode == ZeroTier.EVENT_NETWORK_DOWN) { - // Someone called leave(), we have no assigned addresses, or otherwise cannot use this interface - System.out.println("EVENT_NETWORK_DOWN: nwid=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_NETWORK_REQUESTING_CONFIG) { - // Waiting for network configuration - System.out.println("EVENT_NETWORK_REQUESTING_CONFIG: nwid=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_NETWORK_OK) { - // Config received and this node is authorized for this network - System.out.println("EVENT_NETWORK_OK: nwid=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_NETWORK_ACCESS_DENIED) { - // You are not authorized to join this network - System.out.println("EVENT_NETWORK_ACCESS_DENIED: nwid=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_NETWORK_NOT_FOUND) { - // The virtual network does not exist - System.out.println("EVENT_NETWORK_NOT_FOUND: nwid=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_NETWORK_CLIENT_TOO_OLD) { - // The core version is too old - System.out.println("EVENT_NETWORK_CLIENT_TOO_OLD: nwid=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_PEER_P2P) { - System.out.println("EVENT_PEER_P2P: id=" + Long.toHexString(id)); -/* - ZeroTierPeerDetails details = new ZeroTierPeerDetails(); - ZeroTier.get_peer(id, details); - System.out.println("address="+Long.toHexString(details.address)); - System.out.println("pathCount="+details.pathCount); - System.out.println("version="+details.versionMajor+"."+details.versionMinor+"."+details.versionRev); - System.out.println("latency="+details.latency); // Not relevant - System.out.println("role="+details.role); // Not relevent - // Print all known paths - for (int i=0; i #include diff --git a/src/Controls.cpp b/src/Controls.cpp index fc62ca7..d358409 100644 --- a/src/Controls.cpp +++ b/src/Controls.cpp @@ -25,15 +25,16 @@ #include "Mutex.hpp" #include "OSUtils.hpp" +#include "ZeroTierSockets.h" #include "Debug.hpp" #include "NodeService.hpp" #include "VirtualTap.hpp" #include "Events.hpp" -#include "ZeroTierSockets.h" +#include "Signals.hpp" using namespace ZeroTier; -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA #include #endif @@ -46,13 +47,20 @@ namespace ZeroTier { extern NodeService *service; extern Mutex serviceLock; - extern void (*_userEventCallbackFunc)(void *); +#ifdef ZTS_ENABLE_PYTHON + +#endif +#ifdef ZTS_ENABLE_PINVOKE + extern void (*_userEventCallback)(void *); +#endif +#ifdef ZTS_C_API_ONLY + extern void (*_userEventCallback)(void *); +#endif extern uint8_t allowNetworkCaching; extern uint8_t allowPeerCaching; extern uint8_t allowLocalConf; extern uint8_t disableLocalStorage; // Off by default - -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA // References to JNI objects and VM kept for future callbacks JavaVM *jvm = NULL; jobject objRef = NULL; @@ -110,10 +118,15 @@ int zts_get_node_identity(char *key_pair_str, uint16_t *key_buf_len) } // TODO: This logic should be further generalized in the next API redesign -#ifdef ZTS_PINVOKE +#ifdef ZTS_ENABLE_PYTHON +int zts_start_with_identity(const char *key_pair_str, uint16_t key_buf_len, + PythonDirectorCallbackClass *callback, uint16_t port) +#endif +#ifdef ZTS_ENABLE_PINVOKE int zts_start_with_identity(const char *key_pair_str, uint16_t key_buf_len, CppCallback callback, uint16_t port) -#else +#endif +#ifdef ZTS_C_API_ONLY int zts_start_with_identity(const char *key_pair_str, uint16_t key_buf_len, void (*callback)(void *), uint16_t port) #endif @@ -122,6 +135,9 @@ int zts_start_with_identity(const char *key_pair_str, uint16_t key_buf_len, return ZTS_ERR_ARG; } Mutex::Lock _l(serviceLock); +//#ifdef ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS + _install_signal_handlers(); +//#endif // ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS _lwip_driver_init(); if (service || _getState(ZTS_STATE_NODE_RUNNING)) { // Service is already initialized @@ -132,11 +148,7 @@ int zts_start_with_identity(const char *key_pair_str, uint16_t key_buf_len, // an application restart is required now return ZTS_ERR_SERVICE; } - #ifdef SDK_JNI - _userEventCallbackFunc = callback; -#else - _userEventCallbackFunc = callback; -#endif + _userEventCallback = callback; if (!_isCallbackRegistered()) { // Must have a callback return ZTS_ERR_ARG; @@ -231,13 +243,20 @@ int zts_disable_local_storage(uint8_t disabled) return ZTS_ERR_SERVICE; } -#ifdef ZTS_PINVOKE +#ifdef ZTS_ENABLE_PYTHON +int zts_start(const char *path, PythonDirectorCallbackClass *callback, uint16_t port) +#endif +#ifdef ZTS_ENABLE_PINVOKE int zts_start(const char *path, CppCallback callback, uint16_t port) -#else +#endif +#ifdef ZTS_C_API_ONLY int zts_start(const char *path, void (*callback)(void *), uint16_t port) #endif { Mutex::Lock _l(serviceLock); +//#ifdef ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS + _install_signal_handlers(); +//#endif // ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS _lwip_driver_init(); if (service || _getState(ZTS_STATE_NODE_RUNNING)) { // Service is already initialized @@ -248,11 +267,7 @@ int zts_start(const char *path, void (*callback)(void *), uint16_t port) // an application restart is required now return ZTS_ERR_SERVICE; } -#ifdef SDK_JNI - _userEventCallbackFunc = callback; -#else - _userEventCallbackFunc = callback; -#endif + _userEventCallback = callback; if (!_isCallbackRegistered()) { // Must have a callback return ZTS_ERR_ARG; @@ -300,7 +315,7 @@ int zts_start(const char *path, void (*callback)(void *), uint16_t port) return retval; } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_start( JNIEnv *env, jobject thisObj, jstring path, jobject callback, jint port) { @@ -344,7 +359,7 @@ int zts_stop() } return ZTS_ERR_SERVICE; } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT void JNICALL Java_com_zerotier_libzt_ZeroTier_stop( JNIEnv *env, jobject thisObj) { @@ -357,11 +372,8 @@ int zts_restart() { serviceLock.lock(); // Store callback references -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA static jmethodID _tmpUserCallbackMethodRef = _userCallbackMethodRef; -#else - void (*_tmpUserEventCallbackFunc)(void *); - _tmpUserEventCallbackFunc = _userEventCallbackFunc; #endif int userProvidedPort = 0; std::string userProvidedPath; @@ -388,15 +400,14 @@ int zts_restart() } /* Some of the logic in Java_com_zerotier_libzt_ZeroTier_start is replicated here */ -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA _userCallbackMethodRef = _tmpUserCallbackMethodRef; return zts_start(userProvidedPath.c_str(), NULL, userProvidedPort); #else - return ZTS_ERR_OK; - //return zts_start(userProvidedPath.c_str(), _tmpUserEventCallbackFunc, userProvidedPort); + return ZTS_ERR_OK; #endif } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT void JNICALL Java_com_zerotier_libzt_ZeroTier_restart( JNIEnv *env, jobject thisObj) { @@ -415,7 +426,7 @@ int zts_free() _lwip_driver_shutdown(); return err; } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT void JNICALL Java_com_zerotier_libzt_ZeroTier_free( JNIEnv *env, jobject thisObj) { @@ -423,7 +434,7 @@ JNIEXPORT void JNICALL Java_com_zerotier_libzt_ZeroTier_free( } #endif -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA /* * Called from Java, saves a static reference to the VM so it can be used * later to call a user-specified callback method from C. @@ -431,7 +442,7 @@ JNIEXPORT void JNICALL Java_com_zerotier_libzt_ZeroTier_free( JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_init( JNIEnv *env, jobject thisObj) { - jint rs = env->GetJavaVM(&jvm); + jint rs = env->GetJavaVM(&jvm); return rs != JNI_OK ? ZTS_ERR_GENERAL : ZTS_ERR_OK; } #endif @@ -447,7 +458,7 @@ int zts_join(const uint64_t networkId) } return ZTS_ERR_OK; } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_join( JNIEnv *env, jobject thisObj, jlong networkId) { @@ -466,7 +477,7 @@ int zts_leave(const uint64_t networkId) } return ZTS_ERR_OK; } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_leave( JNIEnv *env, jobject thisObj, jlong networkId) { @@ -485,7 +496,7 @@ int zts_orbit(uint64_t moonWorldId, uint64_t moonSeed) } return ZTS_ERR_OK; } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA #endif int zts_deorbit(uint64_t moonWorldId) @@ -499,7 +510,7 @@ int zts_deorbit(uint64_t moonWorldId) } return ZTS_ERR_OK; } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA #endif int zts_get_6plane_addr(struct zts_sockaddr_storage *addr, const uint64_t networkId, const uint64_t nodeId) @@ -542,14 +553,14 @@ uint64_t zts_generate_adhoc_nwid_from_range(uint16_t startPortOfRange, uint16_t void zts_delay_ms(long milliseconds) { #ifdef __WINDOWS__ - Sleep(milliseconds); + Sleep(milliseconds); #elif _POSIX_C_SOURCE >= 199309L - struct timespec ts; - ts.tv_sec = milliseconds / 1000; - ts.tv_nsec = (milliseconds % 1000) * 1000000; - nanosleep(&ts, NULL); + struct timespec ts; + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = (milliseconds % 1000) * 1000000; + nanosleep(&ts, NULL); #else - usleep(milliseconds * 1000); + usleep(milliseconds * 1000); #endif } diff --git a/src/Events.cpp b/src/Events.cpp index d3f1154..049f9ee 100644 --- a/src/Events.cpp +++ b/src/Events.cpp @@ -19,7 +19,7 @@ #include "concurrentqueue.h" -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA #include #endif @@ -40,6 +40,15 @@ #define ROUTE_EVENT_TYPE(code) code >= ZTS_EVENT_ROUTE_ADDED && code <= ZTS_EVENT_ROUTE_REMOVED #define ADDR_EVENT_TYPE(code) code >= ZTS_EVENT_ADDR_ADDED_IP4 && code <= ZTS_EVENT_ADDR_REMOVED_IP6 +#include +#include + +#ifdef ZTS_ENABLE_PYTHON + #include "Python.h" + PythonDirectorCallbackClass *_userEventCallback = NULL; + void PythonDirectorCallbackClass::on_zerotier_event(struct zts_callback_msg *msg) { } +#endif + namespace ZeroTier { extern NodeService *service; @@ -50,7 +59,12 @@ uint8_t _serviceStateFlags; // Lock to guard access to callback function pointers. Mutex _callbackLock; -void (*_userEventCallbackFunc)(void *); +#ifdef ZTS_ENABLE_PINVOKE + void (*_userEventCallback)(void *); +#endif +#ifdef ZTS_C_API_ONLY + void (*_userEventCallback)(void *); +#endif moodycamel::ConcurrentQueue _callbackMsgQueue; @@ -100,7 +114,12 @@ void _freeEvent(struct zts_callback_msg *msg) void _passDequeuedEventToUser(struct zts_callback_msg *msg) { bool bShouldStopCallbackThread = (msg->eventCode == ZTS_EVENT_STACK_DOWN); -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_PYTHON + PyGILState_STATE state = PyGILState_Ensure(); + _userEventCallback->on_zerotier_event(msg); + PyGILState_Release(state); +#endif +#ifdef ZTS_ENABLE_JAVA if(_userCallbackMethodRef) { JNIEnv *env; #if defined(__ANDROID__) @@ -121,14 +140,19 @@ void _passDequeuedEventToUser(struct zts_callback_msg *msg) id = msg->peer ? msg->peer->address : 0; } env->CallVoidMethod(objRef, _userCallbackMethodRef, id, msg->eventCode); - _freeEvent(msg); } -#else - if (_userEventCallbackFunc) { - _userEventCallbackFunc(msg); - _freeEvent(msg); +#endif // ZTS_ENABLE_JAVA +#ifdef ZTS_ENABLE_PINVOKE + if (_userEventCallback) { + _userEventCallback(msg); } #endif +#ifdef ZTS_C_API_ONLY + if (_userEventCallback) { + _userEventCallback(msg); + } +#endif + _freeEvent(msg); if (bShouldStopCallbackThread) { /* Ensure last possible callback ZTS_EVENT_STACK_DOWN is delivered before callback thread is finally stopped. */ @@ -140,10 +164,10 @@ bool _isCallbackRegistered() { _callbackLock.lock(); bool retval = false; -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA retval = (jvm && objRef && _userCallbackMethodRef); #else - retval = _userEventCallbackFunc; + retval = _userEventCallback; #endif _callbackLock.unlock(); return retval; @@ -152,11 +176,11 @@ bool _isCallbackRegistered() void _clearRegisteredCallback() { _callbackLock.lock(); -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA objRef = NULL; _userCallbackMethodRef = NULL; #else - _userEventCallbackFunc = NULL; + _userEventCallback = NULL; #endif _callbackLock.unlock(); } @@ -237,7 +261,7 @@ void *_runCallbacks(void *thread_id) } zts_delay_ms(ZTS_CALLBACK_PROCESSING_INTERVAL); } -#if SDK_JNI +#if ZTS_ENABLE_JAVA JNIEnv *env; jint rs = jvm->DetachCurrentThread(); pthread_exit(0); diff --git a/src/Events.hpp b/src/Events.hpp index fdb9bd7..a0c07ad 100644 --- a/src/Events.hpp +++ b/src/Events.hpp @@ -29,7 +29,7 @@ #include #endif -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA #include #endif namespace ZeroTier { @@ -40,7 +40,7 @@ namespace ZeroTier { #define ZTS_STATE_CALLBACKS_RUNNING 0x08 #define ZTS_STATE_FREE_CALLED 0x10 -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA // References to JNI objects and VM kept for future callbacks extern JavaVM *jvm; extern jobject objRef; diff --git a/src/NodeService.cpp b/src/NodeService.cpp index fe237ec..4411afe 100644 --- a/src/NodeService.cpp +++ b/src/NodeService.cpp @@ -51,7 +51,7 @@ #define stat _stat #endif -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA #include #endif diff --git a/src/Signals.cpp b/src/Signals.cpp new file mode 100644 index 0000000..8818898 --- /dev/null +++ b/src/Signals.cpp @@ -0,0 +1,70 @@ +/* + * 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 + * + * Custom signal handler + */ + +#include "ZeroTierSockets.h" +#include "Signals.hpp" + +#ifdef ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS + +#include +#include +#include + +void _signal_handler(int signal) +{ + /* + switch(signal) + { + case SIGINT: + fprintf(stderr, "SIGINT\n"); + break; + case SIGABRT: + fprintf(stderr, "SIGABRT\n"); + break; + case SIGILL: + fprintf(stderr, "SIGILL\n"); + break; + case SIGSEGV: + fprintf(stderr, "SIGSEGV\n"); + break; + case SIGFPE: + fprintf(stderr, "SIGFPE\n"); + break; + case SIGTERM: + default: + fprintf(stderr, "SIGTERM\n"); + break; + } + */ + exit(signal); +} + +void _install_signal_handlers() +{ + signal(SIGINT, &_signal_handler); + /* + signal(SIGABRT, &_signal_handler); + signal(SIGFPE, &_signal_handler); + signal(SIGILL, &_signal_handler); + signal(SIGSEGV, &_signal_handler); + signal(SIGTERM, &_signal_handler); + */ +} + +#endif diff --git a/src/Signals.hpp b/src/Signals.hpp new file mode 100644 index 0000000..e1151ce --- /dev/null +++ b/src/Signals.hpp @@ -0,0 +1,37 @@ +/* + * 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 + * + * Custom signal handler + */ + +#ifndef SIGNALS_HPP +#define SIGNALS_HPP + +#ifdef ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS + +/** + * + */ +void _signal_handler(int signal); + +/** + * + */ +void _install_signal_handlers(); + +#endif // ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS + +#endif // _H \ No newline at end of file diff --git a/src/Sockets.cpp b/src/Sockets.cpp index 29c4429..bef540a 100644 --- a/src/Sockets.cpp +++ b/src/Sockets.cpp @@ -23,15 +23,14 @@ #include "lwip/stats.h" #include "ZeroTierSockets.h" -//#include "Events.hpp" -#define ZTS_STATE_NODE_RUNNING 0x01 -#define ZTS_STATE_STACK_RUNNING 0x02 -#define ZTS_STATE_NET_SERVICE_RUNNING 0x04 -#define ZTS_STATE_CALLBACKS_RUNNING 0x08 -#define ZTS_STATE_FREE_CALLED 0x10 +#define ZTS_STATE_NODE_RUNNING 0x01 +#define ZTS_STATE_STACK_RUNNING 0x02 +#define ZTS_STATE_NET_SERVICE_RUNNING 0x04 +#define ZTS_STATE_CALLBACKS_RUNNING 0x08 +#define ZTS_STATE_FREE_CALLED 0x10 -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA #include #endif @@ -45,7 +44,7 @@ extern uint8_t _serviceStateFlags; extern "C" { #endif -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA void ss2zta(JNIEnv *env, struct zts_sockaddr_storage *ss, jobject addr); void zta2ss(JNIEnv *env, struct zts_sockaddr_storage *ss, jobject addr); void ztfdset2fdset(JNIEnv *env, int nfds, jobject src_ztfd_set, zts_fd_set *dest_fd_set); @@ -59,7 +58,7 @@ int zts_socket(const int socket_family, const int socket_type, const int protoco } return lwip_socket(socket_family, socket_type, protocol); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_socket( JNIEnv *env, jobject thisObj, jint family, jint type, jint protocol) { @@ -81,7 +80,7 @@ int zts_connect(int fd, const struct zts_sockaddr *addr, zts_socklen_t addrlen) } return lwip_connect(fd, (sockaddr*)addr, addrlen); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_connect( JNIEnv *env, jobject thisObj, jint fd, jobject addr) { @@ -106,7 +105,8 @@ int zts_bind(int fd, const struct zts_sockaddr *addr, zts_socklen_t addrlen) } return lwip_bind(fd, (sockaddr*)addr, addrlen); } -#ifdef SDK_JNI + +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_bind( JNIEnv *env, jobject thisObj, jint fd, jobject addr) { @@ -125,7 +125,7 @@ int zts_listen(int fd, int backlog) } return lwip_listen(fd, backlog); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_listen( JNIEnv *env, jobject thisObj, jint fd, int backlog) { @@ -141,7 +141,7 @@ int zts_accept(int fd, struct zts_sockaddr *addr, zts_socklen_t *addrlen) } return lwip_accept(fd, (sockaddr*)addr, (socklen_t*)addrlen); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_accept( JNIEnv *env, jobject thisObj, jint fd, jobject addr, jint port) { @@ -162,7 +162,7 @@ int zts_accept4(int fd, struct zts_sockaddr *addr, zts_socklen_t *addrlen, int f return ZTS_ERR_SERVICE; // TODO } #endif -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA #if defined(__linux__) JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_accept4( JNIEnv *env, jobject thisObj, jint fd, jobject addr, jint port, jint flags) @@ -183,7 +183,7 @@ int zts_setsockopt(int fd, int level, int optname, const void *optval,zts_sockle } return lwip_setsockopt(fd, level, optname, optval, optlen); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_setsockopt( JNIEnv *env, jobject thisObj, jint fd, jint level, jint optname, jobject optval) { @@ -236,7 +236,7 @@ int zts_getsockopt(int fd, int level, int optname, void *optval, zts_socklen_t * } return lwip_getsockopt(fd, level, optname, optval, (socklen_t*)optlen); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_getsockopt( JNIEnv *env, jobject thisObj, jint fd, jint level, jint optname, jobject optval) { @@ -300,7 +300,7 @@ int zts_getsockname(int fd, struct zts_sockaddr *addr, zts_socklen_t *addrlen) } return lwip_getsockname(fd, (sockaddr*)addr, (socklen_t*)addrlen); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jboolean JNICALL Java_com_zerotier_libzt_ZeroTier_getsockname(JNIEnv *env, jobject thisObj, jint fd, jobject addr) { @@ -325,7 +325,7 @@ int zts_getpeername(int fd, struct zts_sockaddr *addr, zts_socklen_t *addrlen) } return lwip_getpeername(fd, (sockaddr*)addr, (socklen_t*)addrlen); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_getpeername(JNIEnv *env, jobject thisObj, jint fd, jobject addr) { @@ -343,7 +343,7 @@ int zts_close(int fd) } return lwip_close(fd); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_close( JNIEnv *env, jobject thisObj, jint fd) { @@ -359,7 +359,7 @@ int zts_select(int nfds, zts_fd_set *readfds, zts_fd_set *writefds, zts_fd_set * } return lwip_select(nfds, (fd_set*)readfds, (fd_set*)writefds, (fd_set*)exceptfds, (timeval*)timeout); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_select(JNIEnv *env, jobject thisObj, jint nfds, jobject readfds, jobject writefds, jobject exceptfds, jint timeout_sec, jint timeout_usec) { @@ -403,7 +403,7 @@ int zts_fcntl(int fd, int cmd, int flags) } return lwip_fcntl(fd, cmd, flags); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_fcntl( JNIEnv *env, jobject thisObj, jint fd, jint cmd, jint flags) { @@ -431,7 +431,7 @@ int zts_ioctl(int fd, unsigned long request, void *argp) } return lwip_ioctl(fd, request, argp); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT int JNICALL Java_com_zerotier_libzt_ZeroTier_ioctl( JNIEnv *env, jobject thisObj, jint fd, jlong request, jobject argp) { @@ -466,7 +466,7 @@ ssize_t zts_send(int fd, const void *buf, size_t len, int flags) } return lwip_send(fd, buf, len, flags); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_send( JNIEnv *env, jobject thisObj, jint fd, jbyteArray buf, int flags) { @@ -491,7 +491,7 @@ ssize_t zts_sendto(int fd, const void *buf, size_t len, int flags, } return lwip_sendto(fd, buf, len, flags, (sockaddr*)addr, addrlen); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_sendto( JNIEnv *env, jobject thisObj, jint fd, jbyteArray buf, jint flags, jobject addr) { @@ -512,7 +512,7 @@ ssize_t zts_sendmsg(int fd, const struct msghdr *msg, int flags) } return lwip_sendmsg(fd, msg, flags); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA #endif ssize_t zts_recv(int fd, void *buf, size_t len, int flags) @@ -525,7 +525,7 @@ ssize_t zts_recv(int fd, void *buf, size_t len, int flags) } return lwip_recv(fd, buf, len, flags); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_recv(JNIEnv *env, jobject thisObj, jint fd, jbyteArray buf, jint flags) { @@ -547,7 +547,7 @@ ssize_t zts_recvfrom(int fd, void *buf, size_t len, int flags, } return lwip_recvfrom(fd, buf, len, flags, (sockaddr*)addr, (socklen_t*)addrlen); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_recvfrom( JNIEnv *env, jobject thisObj, jint fd, jbyteArray buf, jint flags, jobject addr) { @@ -572,7 +572,7 @@ ssize_t zts_recvmsg(int fd, struct msghdr *msg, int flags) } return lwip_recvmsg(fd, msg, flags); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA #endif ssize_t zts_read(int fd, void *buf, size_t len) @@ -596,7 +596,7 @@ ssize_t zts_read_offset(int fd, void *buf, size_t offset, size_t len) char *cbuf = (char*)buf; return lwip_read(fd, &(cbuf[offset]), len); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_read(JNIEnv *env, jobject thisObj, jint fd, jbyteArray buf) { @@ -642,7 +642,7 @@ ssize_t zts_write(int fd, const void *buf, size_t len) } return lwip_write(fd, buf, len); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_write__IB(JNIEnv *env, jobject thisObj, jint fd, jbyteArray buf) { @@ -683,7 +683,7 @@ int zts_shutdown(int fd, int how) } return lwip_shutdown(fd, how); } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_shutdown( JNIEnv *env, jobject thisObj, int fd, int how) { @@ -698,7 +698,7 @@ int zts_add_dns_nameserver(struct zts_sockaddr *addr) } return ZTS_ERR_SERVICE; // TODO } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA #endif int zts_del_dns_nameserver(struct zts_sockaddr *addr) @@ -708,7 +708,7 @@ int zts_del_dns_nameserver(struct zts_sockaddr *addr) } return ZTS_ERR_SERVICE; // TODO } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA #endif uint16_t zts_htons(uint16_t n) @@ -750,6 +750,8 @@ uint32_t zts_inet_addr(const char *cp) // Statistics // ////////////////////////////////////////////////////////////////////////////// +#ifdef ZTS_ENABLE_STATS + extern struct stats_ lwip_stats; int zts_get_all_stats(struct zts_stats *statsDest) @@ -785,7 +787,7 @@ int zts_get_all_stats(struct zts_stats *statsDest) return ZTS_ERR_NO_RESULT; #endif } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA // No implementation for JNI #endif @@ -836,7 +838,7 @@ int zts_get_protocol_stats(int protocolType, void *protoStatsDest) return ZTS_ERR_NO_RESULT; #endif } -#ifdef SDK_JNI +#ifdef ZTS_ENABLE_JAVA JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_get_1protocol_1stats( JNIEnv *env, jobject thisObj, jint protocolType, jobject protoStatsObj) { @@ -876,7 +878,9 @@ JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_get_1protocol_1stats( } #endif -#ifdef SDK_JNI +#endif // ZTS_ENABLE_STATS + +#ifdef ZTS_ENABLE_JAVA void ztfdset2fdset(JNIEnv *env, int nfds, jobject src_ztfd_set, zts_fd_set *dest_fd_set) { jclass c = env->GetObjectClass(src_ztfd_set); From 32da07ccb7d95d54605a60e75bcab0c9ea4e3b5d Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Wed, 24 Feb 2021 01:30:23 -0800 Subject: [PATCH 14/25] Remove unnecessary Android example project resource files --- .../drawable-v24/ic_launcher_foreground.xml | 34 ---- .../res/drawable/ic_launcher_background.xml | 170 ------------------ .../app/src/main/res/layout/activity_main.xml | 26 --- .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3056 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 5024 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2096 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2858 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4569 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 7098 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 6464 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 10676 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 9250 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 15523 -> 0 bytes .../app/src/main/res/values/colors.xml | 6 - .../app/src/main/res/values/strings.xml | 3 - .../app/src/main/res/values/styles.xml | 11 -- .../gradle/wrapper/gradle-wrapper.jar | Bin 54708 -> 0 bytes 19 files changed, 260 deletions(-) delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/layout/activity_main.xml delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/values/colors.xml delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/values/strings.xml delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/res/values/styles.xml delete mode 100644 examples/android/ExampleAndroidApp/gradle/wrapper/gradle-wrapper.jar diff --git a/examples/android/ExampleAndroidApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/examples/android/ExampleAndroidApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index c7bd21d..0000000 --- a/examples/android/ExampleAndroidApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/examples/android/ExampleAndroidApp/app/src/main/res/drawable/ic_launcher_background.xml b/examples/android/ExampleAndroidApp/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index d5fccc5..0000000 --- a/examples/android/ExampleAndroidApp/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/android/ExampleAndroidApp/app/src/main/res/layout/activity_main.xml b/examples/android/ExampleAndroidApp/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 6b12ce5..0000000 --- a/examples/android/ExampleAndroidApp/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index eca70cf..0000000 --- a/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index eca70cf..0000000 --- a/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a2f5908281d070150700378b64a84c7db1f97aa1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3056 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 axPAxe%7rDusX+w*00006jj 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⁣ zwiG+x#%j}{0gU~J8;<|!B1@-XaB@{KORFwrYg_8rOv({b0EO#DbeQRm;B6_9=mXGf z-x|VL{zd`)#@yN}HkCSJbjbNlE|zL3Wm9Q8HY`sV)}3%pgN>cL^67{Z;PPL(*wT8N zUjXU{@|*hvm}({wsAC=x0^ok0%UAz0;sogW{B!nDqk|JJ5x~4NfTDgP49^zeu`csl?5mY@JdQdISc zFs!E{^grmkLnUk9 zny~m)1vws@5BFI<-0Tuo2JWX(0v`W|t(wg;s--L47WTvTMz-8l#TL^=OJNRS2?_Qj z3AKT+gvbyBi#H*-tJ%tWD|>EV3wy|8qxfzS!5RW;Jpl5*zo&^UBU=fG#2}UvRyNkK zA06Dy9;K1ca@r2T>yThYgI!ont$(G{6q#2QT+00r_x0(b)gsE`lBB?2gr55gq^D3Fi&p%E(p9>U%bv zkg1Jco(RbyTX7FDHOnl7-O@ zI$AaIl?9NJKPm(WiBP`1-#CB1QzU>&hKm)fpa5DKE{2$X0hGz-0uZ?cyTk(YC!Y&| zL=1VrNERSA5NA2jq7FACfX4JfPyj5XXl1yv0>~s;eF7L2$>&oMqeTFT2m$y7FlkON z_yurD1yIOvA;5C6016pyxBznGUt0kJ&k5r#;&>Jow`r)sp9R~PmK~lz$3xH%LT*1U zJdOyABZ3!FvNoR*vN$5ykHS8f`jA4zV+|L}i1C4`B2c{R0;UdYxaU|H)2avz@ z=mEYc|2S<+(B2Tj+FkX+2D+yFI!k9lWMA61DJ{)e;lum$(;O87?vGJJe!KtK04+N_ zI*P~t@dUb>9Xh{dbyl{-ZQ(UMgz7$|QfL5XSPkskt^NgctYC#;4WcZB1@%@wy@2t3 z2z0DI7&%b$*Aw~abe?GxE`ez@+6hOh-6*8fHRV{1os$EL@}uUZeG4h1&Be`98q*7j z=3-v+lhIjfWVo12!<>%V^a6lTgW3+_#W6n|p*~==zOH7z$0{LSZk(Tpd7EaD04hnA zL;#fxS0aD{`5^&D`}>0Uq?byDD-l2=!wm_bLcUl4gc(% za1p|itVANvFF>hghAS07Im1;IK;|b*W)}VDyI;BIp2=K*yu2a)j?B|f<44NI$NbmJ z#dE0>jI$fMr&@>4kN8MLFb4&2O9fEKaQg%(QO$4_1rVQywG^CmBLh#}_7gKW3vd?| z2?1^&KWq8}8I^_S0|)MowU_pw$q@nl@Nkn$z>BQq_KA^9yaR`(R3u{{Ig;cwt z@AJ^{ODQCm^neroM9nKNUAXi9RCK`OsP_LuR0PUR(YZCCX5dNF6VzcoK&=b^r`W?ltt|*F zpkoae%ZT{C1h~EcFui~b7fF`vb<<~j_VquuUA$}QqIKYELPp#;{u?q8Dz}WAG-(3; zjrm$i%7UbyZMM(Y{>!uJ#vNB?R~B{6Htp=>e*<{fQQ5W7V(1coCWlOON!MzZxhum| ztZBQpGR z;~#ur^&PockKdV{Q6R>o`Pl{0x!DEbpZ7y9Y;*ZvE!*gU`V1W3znva{f=?WO5I&>B z&hw6}tjECtaghm5z|C#%M;Yf_*pI^};h}Vl=^r9EN=tVDj86D;C$jIJ?K7VP+00000NkvXXu0mjf D5i!M* diff --git a/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 459ca609d3ae0d3943ab44cdc27feef9256dc6d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7098 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 diff --git a/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8ca12fe024be86e868d14e91120a6902f8e88ac6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6464 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 B0*L?s diff --git a/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 8e19b410a1b15ff180f3dacac19395fe3046cdec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10676 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 diff --git a/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/examples/android/ExampleAndroidApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 4c19a13c239cb67b8a2134ddd5f325db1d2d5bee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15523 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
{h2A1UXMG-c%hMvHLbF6t}G>KA)H# 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 BIP(Ai diff --git a/examples/android/ExampleAndroidApp/app/src/main/res/values/colors.xml b/examples/android/ExampleAndroidApp/app/src/main/res/values/colors.xml deleted file mode 100644 index 3ab3e9c..0000000 --- a/examples/android/ExampleAndroidApp/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - #3F51B5 - #303F9F - #FF4081 - diff --git a/examples/android/ExampleAndroidApp/app/src/main/res/values/strings.xml b/examples/android/ExampleAndroidApp/app/src/main/res/values/strings.xml deleted file mode 100644 index 09ecae6..0000000 --- a/examples/android/ExampleAndroidApp/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - ExampleAndroidApp - diff --git a/examples/android/ExampleAndroidApp/app/src/main/res/values/styles.xml b/examples/android/ExampleAndroidApp/app/src/main/res/values/styles.xml deleted file mode 100644 index 5885930..0000000 --- a/examples/android/ExampleAndroidApp/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/examples/android/ExampleAndroidApp/gradle/wrapper/gradle-wrapper.jar b/examples/android/ExampleAndroidApp/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 7a3265ee94c0ab25cf079ac8ccdf87f41d455d42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54708 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

_x$+l{{cH8$W#CT From 64a0d5d0d725023fa456407b7c88cd4eba66e286 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 1 Mar 2021 21:10:39 -0800 Subject: [PATCH 15/25] Add working Python wrapper and examples (WIP) --- examples/python/Makefile | 15 +++ examples/python/README.md | 23 ++++ examples/python/example.py | 159 ++++++++++++++++++++++++++ examples/python/zt.i | 30 ----- src/bindings/python/PythonSockets.cpp | 148 ++++++++++++++++++++++++ src/bindings/python/README.md | 4 + src/bindings/python/prototype.py | 103 +++++++++++++++++ src/bindings/python/zt.i | 39 +++++++ 8 files changed, 491 insertions(+), 30 deletions(-) create mode 100644 examples/python/Makefile create mode 100644 examples/python/README.md create mode 100644 examples/python/example.py delete mode 100644 examples/python/zt.i create mode 100644 src/bindings/python/PythonSockets.cpp create mode 100644 src/bindings/python/README.md create mode 100644 src/bindings/python/prototype.py create mode 100644 src/bindings/python/zt.i diff --git a/examples/python/Makefile b/examples/python/Makefile new file mode 100644 index 0000000..2b7cce3 --- /dev/null +++ b/examples/python/Makefile @@ -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 \ No newline at end of file diff --git a/examples/python/README.md b/examples/python/README.md new file mode 100644 index 0000000..640d97b --- /dev/null +++ b/examples/python/README.md @@ -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) diff --git a/examples/python/example.py b/examples/python/example.py new file mode 100644 index 0000000..5a4a759 --- /dev/null +++ b/examples/python/example.py @@ -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: \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) + diff --git a/examples/python/zt.i b/examples/python/zt.i deleted file mode 100644 index 69aa836..0000000 --- a/examples/python/zt.i +++ /dev/null @@ -1,30 +0,0 @@ -/* libzt.i */ - -%begin -%{ -#define SWIG_PYTHON_CAST_MODE -%} - -%include - -#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" diff --git a/src/bindings/python/PythonSockets.cpp b/src/bindings/python/PythonSockets.cpp new file mode 100644 index 0000000..1741168 --- /dev/null +++ b/src/bindings/python/PythonSockets.cpp @@ -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 diff --git a/src/bindings/python/README.md b/src/bindings/python/README.md new file mode 100644 index 0000000..e08e949 --- /dev/null +++ b/src/bindings/python/README.md @@ -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/) diff --git a/src/bindings/python/prototype.py b/src/bindings/python/prototype.py new file mode 100644 index 0000000..bac71ff --- /dev/null +++ b/src/bindings/python/prototype.py @@ -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) diff --git a/src/bindings/python/zt.i b/src/bindings/python/zt.i new file mode 100644 index 0000000..8ddefe5 --- /dev/null +++ b/src/bindings/python/zt.i @@ -0,0 +1,39 @@ +/* libzt.i */ + +%begin +%{ +#define SWIG_PYTHON_CAST_MODE +%} + +%include + +#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" From 66da0495a49716b6e460640eacfa3e6b63d8510c Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 1 Mar 2021 21:39:36 -0800 Subject: [PATCH 16/25] Change C# wrapper extension from cxx to cpp --- src/bindings/csharp/{zt_wrap.cxx => zt_wrap.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/bindings/csharp/{zt_wrap.cxx => zt_wrap.cpp} (100%) diff --git a/src/bindings/csharp/zt_wrap.cxx b/src/bindings/csharp/zt_wrap.cpp similarity index 100% rename from src/bindings/csharp/zt_wrap.cxx rename to src/bindings/csharp/zt_wrap.cpp From 62354e142e32352af78e29040d9e8456a7c39986 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 1 Mar 2021 22:34:12 -0800 Subject: [PATCH 17/25] Improvements to language binding facilities --- include/ZeroTierSockets.h | 82 ++++++++------------------------------- src/Controls.cpp | 8 ++-- src/Sockets.cpp | 32 +++++++-------- 3 files changed, 36 insertions(+), 86 deletions(-) diff --git a/include/ZeroTierSockets.h b/include/ZeroTierSockets.h index 8f95c8d..917c943 100644 --- a/include/ZeroTierSockets.h +++ b/include/ZeroTierSockets.h @@ -25,9 +25,11 @@ ////////////////////////////////////////////////////////////////////////////// #ifdef ZTS_ENABLE_PYTHON - /* In some situations (Python comes to mind) a signal may not make its - way to libzt, for this reason we make sure to define a custom signal handler - that can at least process SIGTERMs */ + /** + * In some situations (Python comes to mind) a signal may not make its + * way to libzt, for this reason we make sure to define a custom signal + * handler that can at least process SIGTERMs + */ #define ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS 1 #endif @@ -842,7 +844,7 @@ struct zts_peer_list }; ////////////////////////////////////////////////////////////////////////////// -// Python bits // +// Python Bindings (Subset of regular socket API) // ////////////////////////////////////////////////////////////////////////////// #ifdef ZTS_ENABLE_PYTHON @@ -867,67 +869,15 @@ public: extern PythonDirectorCallbackClass *_userEventCallback; -/** - * @brief - * - * @param fd - * @param - * @param - * @param - * - * @return - */ -int zts_sock_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); +PyObject * zts_py_accept(int fd); +int zts_py_listen(int fd, int backlog); +PyObject * zts_py_recv(int fd, int len, int flags); +int zts_py_send(int fd, PyObject *buf, int len, int flags); +int zts_py_close(int fd); -/** - * @brief - * - * @param fd - * @param - * @param - * @param - * - * @return - */ -int zts_sock_connect(int fd, int family, int type, PyObject *addro); - -/** - * @brief - * - * @param fd - * @param - * @param - * @param - * - * @return - */ -PyObject * zts_sock_accept(int fd); - -/** - * @brief - * - * @param fd - * @param - * @param - * @param - * - * @return - */ -PyObject * zts_sock_recv(int fd, int len, int flags); - -/** - * @brief - * - * @param fd - * @param - * @param - * @param - * - * @return - */ -int zts_sock_send(int fd, PyObject *buf, int len, int flags); - -#endif +#endif // ZTS_ENABLE_PYTHON ////////////////////////////////////////////////////////////////////////////// // ZeroTier Service Controls // @@ -1436,8 +1386,6 @@ struct zts_stats { struct zts_stats_proto nd6; }; -#endif - /** * @brief Return all statistical counters for all protocols (inefficient) * @@ -1455,6 +1403,8 @@ ZTS_API int ZTCALL zts_get_all_stats(struct zts_stats *statsDest); */ ZTS_API int ZTCALL zts_get_protocol_stats(int protocolType, void *protoStatsDest); +#endif // ZTS_ENABLE_STATS + ////////////////////////////////////////////////////////////////////////////// // Socket API // ////////////////////////////////////////////////////////////////////////////// diff --git a/src/Controls.cpp b/src/Controls.cpp index d358409..d2f64ab 100644 --- a/src/Controls.cpp +++ b/src/Controls.cpp @@ -135,9 +135,9 @@ int zts_start_with_identity(const char *key_pair_str, uint16_t key_buf_len, return ZTS_ERR_ARG; } Mutex::Lock _l(serviceLock); -//#ifdef ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS +#ifdef ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS _install_signal_handlers(); -//#endif // ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS +#endif // ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS _lwip_driver_init(); if (service || _getState(ZTS_STATE_NODE_RUNNING)) { // Service is already initialized @@ -254,9 +254,9 @@ int zts_start(const char *path, void (*callback)(void *), uint16_t port) #endif { Mutex::Lock _l(serviceLock); -//#ifdef ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS +#ifdef ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS _install_signal_handlers(); -//#endif // ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS +#endif // ZTS_ENABLE_CUSTOM_SIGNAL_HANDLERS _lwip_driver_init(); if (service || _getState(ZTS_STATE_NODE_RUNNING)) { // Service is already initialized diff --git a/src/Sockets.cpp b/src/Sockets.cpp index bef540a..c4afe63 100644 --- a/src/Sockets.cpp +++ b/src/Sockets.cpp @@ -259,7 +259,7 @@ JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_getsockopt( else { retval = zts_getsockopt(fd, level, optname, &optval_int, &optlen); } - + if (optname == SO_BROADCAST || optname == SO_KEEPALIVE || optname == SO_REUSEADDR @@ -307,7 +307,7 @@ JNIEXPORT jboolean JNICALL Java_com_zerotier_libzt_ZeroTier_getsockname(JNIEnv * struct zts_sockaddr_storage ss; zts_socklen_t addrlen = sizeof(struct zts_sockaddr_storage); int retval = zts_getsockname(fd, (struct zts_sockaddr *)&ss, &addrlen); - ss2zta(env, &ss, addr); + ss2zta(env, &ss, addr); return retval > -1 ? retval : -(zts_errno); } #endif @@ -331,7 +331,7 @@ JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_getpeername(JNIEnv *env, { struct zts_sockaddr_storage ss; int retval = zts_getpeername(fd, (struct zts_sockaddr *)&ss, (zts_socklen_t*)sizeof(struct zts_sockaddr_storage)); - ss2zta(env, &ss, addr); + ss2zta(env, &ss, addr); return retval > -1 ? retval : -(zts_errno); } #endif @@ -391,7 +391,7 @@ JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_select(JNIEnv *env, jobj } if (exceptfds) { fdset2ztfdset(env, nfds, &_exceptfds, exceptfds); - } + } return retval > -1 ? retval : -(zts_errno); } #endif @@ -438,7 +438,7 @@ JNIEXPORT int JNICALL Java_com_zerotier_libzt_ZeroTier_ioctl( int retval = ZTS_ERR_OK; if (request == FIONREAD) { int bytesRemaining = 0; - retval = zts_ioctl(fd, request, &bytesRemaining); + retval = zts_ioctl(fd, request, &bytesRemaining); // set value in general object jclass c = env->GetObjectClass(argp); if (!c) { @@ -450,7 +450,7 @@ JNIEXPORT int JNICALL Java_com_zerotier_libzt_ZeroTier_ioctl( if (request == FIONBIO) { // TODO: double check int meaninglessVariable = 0; - retval = zts_ioctl(fd, request, &meaninglessVariable); + retval = zts_ioctl(fd, request, &meaninglessVariable); } return retval > -1 ? retval : -(zts_errno); } @@ -472,12 +472,12 @@ JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_send( { void *data = env->GetPrimitiveArrayCritical(buf, NULL); int retval = zts_send(fd, data, env->GetArrayLength(buf), flags); - env->ReleasePrimitiveArrayCritical(buf, data, 0); + env->ReleasePrimitiveArrayCritical(buf, data, 0); return retval > -1 ? retval : -(zts_errno); } #endif -ssize_t zts_sendto(int fd, const void *buf, size_t len, int flags, +ssize_t zts_sendto(int fd, const void *buf, size_t len, int flags, const struct zts_sockaddr *addr,zts_socklen_t addrlen) { if (!addr || !buf) { @@ -500,7 +500,7 @@ JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_sendto( zta2ss(env, &ss, addr); zts_socklen_t addrlen = ss.ss_family == ZTS_AF_INET ? sizeof(struct zts_sockaddr_in) : sizeof(struct zts_sockaddr_in6); int retval = zts_sendto(fd, data, env->GetArrayLength(buf), flags, (struct zts_sockaddr *)&ss, addrlen); - env->ReleasePrimitiveArrayCritical(buf, data, 0); + env->ReleasePrimitiveArrayCritical(buf, data, 0); return retval > -1 ? retval : -(zts_errno); } #endif @@ -536,7 +536,7 @@ JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_recv(JNIEnv *env, jobjec } #endif -ssize_t zts_recvfrom(int fd, void *buf, size_t len, int flags, +ssize_t zts_recvfrom(int fd, void *buf, size_t len, int flags, struct zts_sockaddr *addr, zts_socklen_t *addrlen) { if (!buf) { @@ -556,7 +556,7 @@ JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_recvfrom( void *data = env->GetPrimitiveArrayCritical(buf, NULL); int retval = zts_recvfrom(fd, data, env->GetArrayLength(buf), flags, (struct zts_sockaddr *)&ss, &addrlen); env->ReleasePrimitiveArrayCritical(buf, data, 0); - ss2zta(env, &ss, addr); + ss2zta(env, &ss, addr); return retval > -1 ? retval : -(zts_errno); } #endif @@ -602,7 +602,7 @@ JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_read(JNIEnv *env, jobjec { void *data = env->GetPrimitiveArrayCritical(buf, NULL); int retval = zts_read(fd, data, env->GetArrayLength(buf)); - env->ReleasePrimitiveArrayCritical(buf, data, 0); + env->ReleasePrimitiveArrayCritical(buf, data, 0); return retval > -1 ? retval : -(zts_errno); } JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_read_1offset(JNIEnv *env, jobject thisObj, @@ -610,7 +610,7 @@ JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_read_1offset(JNIEnv *env { void *data = env->GetPrimitiveArrayCritical(buf, NULL); int retval = zts_read_offset(fd, data, offset, len); - env->ReleasePrimitiveArrayCritical(buf, data, 0); + env->ReleasePrimitiveArrayCritical(buf, data, 0); return retval > -1 ? retval : -(zts_errno); } JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_read_1length(JNIEnv *env, jobject thisObj, @@ -618,7 +618,7 @@ JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_read_1length(JNIEnv *env { void *data = env->GetPrimitiveArrayCritical(buf, NULL); int retval = zts_read(fd, data, len); - env->ReleasePrimitiveArrayCritical(buf, data, 0); + env->ReleasePrimitiveArrayCritical(buf, data, 0); return retval > -1 ? retval : -(zts_errno); } #endif @@ -648,7 +648,7 @@ JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_write__IB(JNIEnv *env, j { void *data = env->GetPrimitiveArrayCritical(buf, NULL); int retval = zts_write(fd, data, env->GetArrayLength(buf)); - env->ReleasePrimitiveArrayCritical(buf, data, 0); + env->ReleasePrimitiveArrayCritical(buf, data, 0); return retval > -1 ? retval : -(zts_errno); } JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_write_1offset(JNIEnv *env, jobject thisObj, @@ -656,7 +656,7 @@ JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_write_1offset(JNIEnv *en { void *data = env->GetPrimitiveArrayCritical(&(buf[offset]), NULL); // PENDING: check? int retval = zts_write(fd, data, len); - env->ReleasePrimitiveArrayCritical(buf, data, 0); + env->ReleasePrimitiveArrayCritical(buf, data, 0); return retval > -1 ? retval : -(zts_errno); } JNIEXPORT jint JNICALL Java_com_zerotier_libzt_ZeroTier_write_1byte(JNIEnv *env, jobject thisObj, From 9bca3270bca30e6dbd8069d5871f164a8b72ad30 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 2 Mar 2021 01:53:03 -0800 Subject: [PATCH 18/25] Overhaul build system and documentation --- CMakeLists.txt | 1015 ++++++++++++++++++++++++-------------------- Makefile | 127 ------ README | 23 + README.md | 430 ++++--------------- build.ps1 | 206 +++++++++ build.sh | 543 ++++++++++++++++++++++++ dist.ps1 | 175 -------- dist.sh | 737 -------------------------------- examples/README.md | 58 +-- 9 files changed, 1409 insertions(+), 1905 deletions(-) delete mode 100644 Makefile create mode 100644 README create mode 100644 build.ps1 create mode 100755 build.sh delete mode 100644 dist.ps1 delete mode 100755 dist.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e9fd3d..e385805 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,60 +2,222 @@ cmake_minimum_required(VERSION 3.0) project(zt) find_package(Threads) -set(requiredlibs) - -if(CENTRAL_API) - find_package(CURL) - if(CURL_FOUND) - include_directories(${CURL_INCLUDE_DIR}) - set(requiredlibs ${requiredlibs} ${CURL_LIBRARIES}) - else(CURL_FOUND) - message( - FATAL_ERROR "Could not find the CURL library and development files.") - endif(CURL_FOUND) -endif() - # ----------------------------------------------------------------------------- -# | PLATFORM/FEATURE AND IDE DETECTION | +# | PLATFORM DETECTION | # ----------------------------------------------------------------------------- -if(${CMAKE_SYSTEM_NAME} MATCHES "Android") - set(BUILDING_ANDROID TRUE) +# Apple +if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(BUILD_DARWIN TRUE) endif() + +# macOS +if(BUILD_DARWIN AND NOT BUILD_IOS_FRAMEWORK) + set(BUILD_MACOS TRUE) +endif() + +if(${CMAKE_GENERATOR} STREQUAL "Xcode") + set(IN_XCODE TRUE) + set(XCODE_EMIT_EFFECTIVE_PLATFORM_NAME ON) +endif() + +# Target names +if(IN_XCODE) + set(XCODE_FRAMEWORK_NAME ${PROJECT_NAME}) +endif() + +# Windows if(WIN32) - set(BUILDING_WIN32 TRUE) + set(BUILD_WIN32 TRUE) endif() if("${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)") - set(BUILDING_WIN64 TRUE) + set(BUILD_WIN64 TRUE) endif() -if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - set(BUILDING_DARWIN TRUE) +if(BUILD_WIN32 + OR BUILD_WIN64 + OR MSVC) + set(BUILD_WIN TRUE) endif() -if(IOS_FRAMEWORK) - set(BUILDING_IOS TRUE) + +# Linux +# if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + +# ----------------------------------------------------------------------------- +# | SOURCE DIRECTORIES | +# ----------------------------------------------------------------------------- + +set(PROJ_DIR ${PROJECT_SOURCE_DIR}) +set(LWIP_SRC_DIR "${PROJ_DIR}/ext/lwip/src") +set(ZTO_SRC_DIR "${PROJ_DIR}/ext/ZeroTierOne") +set(LIBZT_SRC_DIR "${PROJ_DIR}/src") +set(CMAKE_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/bin) +set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/lib) +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}) +set(INTERMEDIATE_LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib/intermediate) +if(UNIX) + set(LWIP_PORT_DIR ${PROJ_DIR}/ext/lwip-contrib/ports/unix/port) endif() -if(BUILDING_DARWIN AND NOT IOS_FRAMEWORK) - set(BUILDING_MACOS TRUE) + +if(BUILD_WIN) + set(LWIP_PORT_DIR ${PROJ_DIR}/ext/lwip-contrib/ports/win32) endif() -if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") - set(BUILDING_LINUX TRUE) + +# ----------------------------------------------------------------------------- +# | INCLUDE DIRECTORIES | +# ----------------------------------------------------------------------------- + +# ZeroTier +include_directories(${ZTO_SRC_DIR}) +include_directories(${ZTO_SRC_DIR}/include) +include_directories(${ZTO_SRC_DIR}/node) +include_directories(${ZTO_SRC_DIR}/osdep) +# ZeroTier (ext) +include_directories(${ZTO_SRC_DIR}/ext/miniupnpc) +include_directories(${ZTO_SRC_DIR}/ext/libnatpmp) +# libzt +include_directories(${PROJ_DIR}/src) +include_directories(${PROJ_DIR}/include) +# libzt (ext) +include_directories(${PROJ_DIR}/ext/concurrentqueue) +include_directories(${LWIP_SRC_DIR}/include) +include_directories(${LWIP_PORT_DIR}/include) + +# ----------------------------------------------------------------------------- +# | TARGET AND VARIANT SELECTION | +# ----------------------------------------------------------------------------- + +# C# language bindings (libzt.dll/dylib/so) +if (ZTS_ENABLE_PINVOKE) + # Features + set(BUILD_STATIC_LIB FALSE) + set(BUILD_SHARED_LIB TRUE) + set(BUILD_EXAMPLES FALSE) + set(BUILD_HOST_SELFTEST FALSE) + set(INSTALLABLE FALSE) + # Sources and libraries + set(ZTS_SWIG_WRAPPER_FILE ${LIBZT_SRC_DIR}/bindings/csharp/*.cpp) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DZTS_ENABLE_PINVOKE=1") endif() -if(${CMAKE_GENERATOR} STREQUAL "Xcode") - set(IN_XCODE TRUE) - set(XCODE_EMIT_EFFECTIVE_PLATFORM_NAME ON) + +# Python language bindings (_libzt.so) +if (ZTS_ENABLE_PYTHON) + # Features + set(ZTS_ENABLE_PYTHON TRUE) + #set(ZTS_ENABLE_STATS FALSE) + # Targets + set(BUILD_STATIC_LIB FALSE) + set(BUILD_SHARED_LIB TRUE) + set(BUILD_EXAMPLES FALSE) + set(BUILD_HOST_SELFTEST FALSE) + set(INSTALLABLE FALSE) + # Sources and libraries + find_package(PythonLibs REQUIRED) + include_directories(${PYTHON_INCLUDE_DIRS}) + set(ZTS_SWIG_WRAPPER_FILE ${LIBZT_SRC_DIR}/bindings/python/*.cpp) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DZTS_ENABLE_PYTHON=1") endif() -if(BUILDING_WIN32 - OR BUILDING_WIN64 - OR MSVC) - set(BUILDING_WIN TRUE) + +# Java language bindings +if (ZTS_ENABLE_JAVA) + set(BUILD_STATIC_LIB FALSE) + set(BUILD_SHARED_LIB TRUE) + set(BUILD_HOST_EXAMPLES FALSE) + set(BUILD_HOST_SELFTEST FALSE) + set(ZTS_ENABLE_STATS FALSE) + set(INSTALLABLE FALSE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DZTS_ENABLE_JAVA=1") endif() -if(NOT BUILDING_ANDROID AND NOT IN_XCODE) - set(SHOULD_BUILD_TESTS TRUE) + +# All native targets for this host +if(BUILD_HOST) + set(BUILD_STATIC_LIB TRUE) + set(BUILD_SHARED_LIB TRUE) + set(BUILD_HOST_EXAMPLES TRUE) + set(BUILD_HOST_SELFTEST TRUE) + set(ZTS_ENABLE_STATS TRUE) + set(INSTALLABLE TRUE) endif() -if(BUILDING_WIN32 - OR BUILDING_WIN64 - OR MSVC) - set(BUILDING_WIN TRUE) + +# CI +if(BUILD_HOST_SELFTEST_ONLY) + set(BUILD_STATIC_LIB TRUE) + set(BUILD_SHARED_LIB FALSE) + set(BUILD_HOST_EXAMPLES FALSE) + set(BUILD_HOST_SELFTEST TRUE) + set(ZTS_ENABLE_STATS FALSE) + set(INSTALLABLE FALSE) +endif() + +# Android AAR containing libzt.so +if(${CMAKE_SYSTEM_NAME} MATCHES "Android") + set(BUILD_ANDROID TRUE) + set(BUILD_STATIC_LIB FALSE) + set(BUILD_SHARED_LIB TRUE) + set(BUILD_SELFTEST FALSE) + set(BUILD_EXAMPLES FALSE) + set(INSTALLABLE FALSE) +endif() + +if(BUILD_MACOS_FRAMEWORK) + set(BUILD_STATIC_LIB TRUE) + set(BUILD_SHARED_LIB TRUE) + set(BUILD_SELFTEST FALSE) + set(BUILD_EXAMPLES FALSE) + set(INSTALLABLE FALSE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DOMIT_JSON_SUPPORT=1") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DOMIT_JSON_SUPPORT=1") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS "$(ARCHS_STANDARD)") + set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO) + include_directories( + "/Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk/usr/include/") +endif() + +if(BUILD_IOS_FRAMEWORK) + set(BUILD_STATIC_LIB TRUE) + set(BUILD_SHARED_LIB TRUE) + set(BUILD_SELFTEST FALSE) + set(BUILD_EXAMPLES FALSE) + set(INSTALLABLE FALSE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DOMIT_JSON_SUPPORT=1") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DOMIT_JSON_SUPPORT=1") + set(DEVROOT + "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer") + if(IOS_ARM64) + set(CMAKE_OSX_ARCHITECTURES arm64) + set(SDKVER "11.4") + endif() + if(IOS_ARMV7) + # armv7 armv7s + set(CMAKE_OSX_ARCHITECTURES "$(ARCHS_STANDARD_32_BIT)") + set(SDKVER "10.0") + endif() + set(SDKROOT "${DEVROOT}/SDKs/iPhoneOS${SDKVER}.sdk") + if(EXISTS ${SDKROOT}) + set(CMAKE_OSX_SYSROOT "${SDKROOT}") + else() + message("Warning, iOS Base SDK path not found: " ${SDKROOT}) + endif() +endif() + +if(ZTS_ENABLE_STATS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DZTS_ENABLE_STATS=1") +endif() + +# ----------------------------------------------------------------------------- +# | HACKS TO GET THIS TO WORK ON WINDOWS | +# ----------------------------------------------------------------------------- + +if(BUILD_WIN) + # Possibly a CMake limitation? -- Can't share target output names + set(STATIC_LIB_NAME ${PROJECT_NAME}-static) + set(STATIC_LIB_OUTPUT_NAME ${PROJECT_NAME}-static) + set(DYNAMIC_LIB_NAME ${PROJECT_NAME}-shared) + set(DYNAMIC_LIB_OUTPUT_NAME ${PROJECT_NAME}-shared) +else() + set(STATIC_LIB_NAME ${PROJECT_NAME}-static) + set(STATIC_LIB_OUTPUT_NAME ${PROJECT_NAME}) + set(DYNAMIC_LIB_NAME ${PROJECT_NAME}-shared) + set(DYNAMIC_LIB_OUTPUT_NAME ${PROJECT_NAME}) endif() # ----------------------------------------------------------------------------- @@ -63,380 +225,335 @@ endif() # ----------------------------------------------------------------------------- if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "debug") - set(DEBUG_BUILD ON) - set(CMAKE_VERBOSE_MAKEFILE ON) - set(CMAKE_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/bin) - set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/lib) + set(BUILD_DEBUG ON) + set(CMAKE_VERBOSE_MAKEFILE ON) + set(DEBUG_OPTIMIZATION "-O3") endif() if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "release") - set(RELEASE_BUILD ON) - set(CMAKE_VERBOSE_MAKEFILE OFF) - set(CMAKE_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/bin) - set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/lib) + set(BUILD_RELEASE ON) + set(CMAKE_VERBOSE_MAKEFILE OFF) + set(RELEASE_OPTIMIZATION "-O3") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wno-everything -w") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-everything -w") endif() -set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}) -set(INTERMEDIATE_LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib/intermediate) - # ----------------------------------------------------------------------------- -# | LIBRARY NAMES | +# | EXAMPLES | # ----------------------------------------------------------------------------- -if(IN_XCODE) - set(XCODE_FRAMEWORK_NAME ${PROJECT_NAME}) -endif() +if(BUILD_HOST_EXAMPLES) + add_executable(nonblockingclient + ${PROJ_DIR}/examples/cpp/nonblockingclient.cpp) + target_link_libraries(nonblockingclient ${STATIC_LIB_NAME}) -if(BUILDING_WIN) - # Possibly a CMake limitation? -- Can't share target output names - set(STATIC_LIB_NAME ${PROJECT_NAME}-static) - set(STATIC_LIB_OUTPUT_NAME ${PROJECT_NAME}-static) - set(DYNAMIC_LIB_NAME ${PROJECT_NAME}-shared) - set(DYNAMIC_LIB_OUTPUT_NAME ${PROJECT_NAME}-shared) -else() - set(STATIC_LIB_NAME ${PROJECT_NAME}-static) - set(STATIC_LIB_OUTPUT_NAME ${PROJECT_NAME}) - set(DYNAMIC_LIB_NAME ${PROJECT_NAME}-shared) - set(DYNAMIC_LIB_OUTPUT_NAME ${PROJECT_NAME}) + add_executable(nonblockingserver + ${PROJ_DIR}/examples/cpp/nonblockingserver.cpp) + target_link_libraries(nonblockingserver ${STATIC_LIB_NAME}) + + add_executable(earthtest + ${PROJ_DIR}/examples/cpp/earthtest.cpp) + target_link_libraries(earthtest ${STATIC_LIB_NAME}) + + add_executable(adhoc + ${PROJ_DIR}/examples/cpp/adhoc.cpp) + target_link_libraries(adhoc ${STATIC_LIB_NAME}) + + add_executable(comprehensive + ${PROJ_DIR}/examples/cpp/comprehensive.cpp) + target_link_libraries(comprehensive ${STATIC_LIB_NAME}) + + add_executable(client + ${PROJ_DIR}/examples/cpp/client.cpp) + target_link_libraries(client ${STATIC_LIB_NAME}) + + add_executable(server + ${PROJ_DIR}/examples/cpp/server.cpp) + target_link_libraries(server ${STATIC_LIB_NAME}) + + add_executable(keymanagement + ${PROJ_DIR}/examples/cpp/keymanagement.cpp) + target_link_libraries(keymanagement ${STATIC_LIB_NAME}) + + if(CENTRAL_API) + add_executable(centralapi ${PROJ_DIR}/examples/cpp/centralapi.cpp) + target_link_libraries(centralapi ${STATIC_LIB_NAME}) + endif() endif() # ----------------------------------------------------------------------------- # | FLAGS | # ----------------------------------------------------------------------------- -set(SILENCE "${SILENCE} -Wno-unused-parameter") -set(SILENCE "${SILENCE} -Wno-format") -set(SILENCE "${SILENCE} -Wno-tautological-constant-out-of-range-compare ") -set(SILENCE "${SILENCE} -Wno-macro-redefined") -set(SILENCE "${SILENCE} -Wno-parentheses-equality") -set(SILENCE "${SILENCE} -Wno-sign-compare") -set(SILENCE "${SILENCE} -Wno-unused-variable") -set(SILENCE "${SILENCE} -Wno-missing-field-initializers") -set(SILENCE "${SILENCE} -Wno-unused-parameter") - set(ZT_FLAGS "${ZT_FLAGS} -DZT_USE_MINIUPNPC=1") set(ZT_FLAGS "${ZT_FLAGS} -DZT_SOFTWARE_UPDATE_DEFAULT=0") set(ZT_FLAGS "${ZT_FLAGS} -D_USING_LWIP_DEFINITIONS_=0") set(ZT_FLAGS "${ZT_FLAGS} -DZT_SDK=1") -if(DEBUG_BUILD) - set(LWIP_FLAGS "${LWIP_FLAGS} -DLWIP_DBG_TYPES_ON=128") - set(LWIP_FLAGS "${LWIP_FLAGS} -DSOCKETS_DEBUG=128") - # set (LWIP_FLAGS "${LWIP_FLAGS} -DLWIP_STATS_LARGE=1") set (LWIP_FLAGS - # "${LWIP_FLAGS} -DLWIP_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DLINK_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DETHARP_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DIPFRAG_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DIP_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DICMP_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DIGMP_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DUDP_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DTCP_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DSYS_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DIP6_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DICMP6_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DIP6_FRAG_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DMLD6_STATS=1") - set(LWIP_FLAGS "${LWIP_FLAGS} -DND6_STATS=1") +if(BUILD_DEBUG) + set(LWIP_FLAGS "${LWIP_FLAGS} -DLWIP_DBG_TYPES_ON=128") + set(LWIP_FLAGS "${LWIP_FLAGS} -DSOCKETS_DEBUG=128") + # set (LWIP_FLAGS "${LWIP_FLAGS} -DLWIP_STATS_LARGE=1") set (LWIP_FLAGS + # "${LWIP_FLAGS} -DLWIP_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DLINK_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DETHARP_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DIPFRAG_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DIP_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DICMP_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DIGMP_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DUDP_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DTCP_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DSYS_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DIP6_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DICMP6_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DIP6_FRAG_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DMLD6_STATS=1") + set(LWIP_FLAGS "${LWIP_FLAGS} -DND6_STATS=1") else() - set(LWIP_FLAGS "${LWIP_FLAGS} -DLWIP_DBG_TYPES_ON=0") + set(LWIP_FLAGS "${LWIP_FLAGS} -DLWIP_DBG_TYPES_ON=0") endif() -set(DEBUG_OPTIMIZATION "-O3") -set(RELEASE_OPTIMIZATION "-O3") - -if(BUILDING_WIN) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc -DNOMINMAX") +if(BUILD_WIN) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc -DNOMINMAX") else() - set(CMAKE_C_FLAGS - "${CMAKE_C_FLAGS} \ + set(CMAKE_C_FLAGS + "${CMAKE_C_FLAGS} \ ${ZT_FLAGS} \ -fstack-protector") - set(CMAKE_C_FLAGS_DEBUG - "${CMAKE_C_FLAGS_DEBUG} \ + set(CMAKE_C_FLAGS_DEBUG + "${CMAKE_C_FLAGS_DEBUG} \ ${DEBUG_OPTIMIZATION} \ -DLWIP_DEBUG=1 -DLIBZT_DEBUG=1") - set(CMAKE_C_FLAGS_RELEASE - "${SILENCE} \ - ${CMAKE_C_FLAGS_RELEASE} \ + set(CMAKE_C_FLAGS_RELEASE + "${CMAKE_C_FLAGS_RELEASE} \ ${RELEASE_OPTIMIZATION} \ -fstack-protector") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ ${ZT_FLAGS} -Wall -Wextra -std=c++11") - set(CMAKE_CXX_FLAGS_DEBUG - "${CMAKE_CXX_FLAGS_DEBUG} \ + set(CMAKE_CXX_FLAGS_DEBUG + "${CMAKE_CXX_FLAGS_DEBUG} \ ${DEBUG_OPTIMIZATION} \ -DLWIP_DEBUG=1 -DLIBZT_DEBUG=1") - set(CMAKE_CXX_FLAGS_RELEASE - "${CMAKE_CXX_FLAGS_RELEASE} \ - ${SILENCE} \ + set(CMAKE_CXX_FLAGS_RELEASE + "${CMAKE_CXX_FLAGS_RELEASE} \ ${RELEASE_OPTIMIZATION}") endif() # WINDOWS-specific MSVC flags and libraries -if(BUILDING_WIN) - # 32-bit - if(NOT BUILDING_WIN64) - set(WINLIBDIR, - "C:/Program Files (x86)/Windows Kits/10/Lib/10.0.16299.0/um/x86") - endif() - # 64-bit - if(BUILDING_WIN64) - set(WINLIBDIR, - "C:/Program Files (x86)/Windows Kits/10/Lib/10.0.16299.0/um/x64") - endif() - # find_library (ws2_32_LIBRARY_PATH NAMES WS2_32 HINTS ${WINLIBDIR}) - # find_library (shlwapi_LIBRARY_PATH NAMES ShLwApi HINTS ${WINLIBDIR}) - set(ws2_32_LIBRARY_PATH "${WINLIBDIR}/WS2_32.Lib") - set(shlwapi_LIBRARY_PATH "${WINLIBDIR}/ShLwApi.Lib") - set(iphlpapi_LIBRARY_PATH "${WINLIBDIR}/iphlpapi.Lib") - message(STATUS ${WINLIBDIR}) - message(STATUS "WS2_32=${ws2_32_LIBRARY_PATH}") - message(STATUS "ShLwApi=${shlwapi_LIBRARY_PATH}") - message(STATUS "liphlpapi=${iphlpapi_LIBRARY_PATH}") - add_definitions(-DZT_SDK=1) - add_definitions(-DADD_EXPORTS=1) +if(BUILD_WIN) + # 32-bit + if(NOT BUILD_WIN64) + set(WINLIBDIR, + "C:/Program Files (x86)/Windows Kits/10/Lib/10.0.16299.0/um/x86") + endif() + # 64-bit + if(BUILD_WIN64) + set(WINLIBDIR, + "C:/Program Files (x86)/Windows Kits/10/Lib/10.0.16299.0/um/x64") + endif() + # find_library (ws2_32_LIBRARY_PATH NAMES WS2_32 HINTS ${WINLIBDIR}) + # find_library (shlwapi_LIBRARY_PATH NAMES ShLwApi HINTS ${WINLIBDIR}) + set(ws2_32_LIBRARY_PATH "${WINLIBDIR}/WS2_32.Lib") + set(shlwapi_LIBRARY_PATH "${WINLIBDIR}/ShLwApi.Lib") + set(iphlpapi_LIBRARY_PATH "${WINLIBDIR}/iphlpapi.Lib") + message(STATUS ${WINLIBDIR}) + message(STATUS "WS2_32=${ws2_32_LIBRARY_PATH}") + message(STATUS "ShLwApi=${shlwapi_LIBRARY_PATH}") + message(STATUS "liphlpapi=${iphlpapi_LIBRARY_PATH}") + add_definitions(-DZT_SDK=1) + add_definitions(-DADD_EXPORTS=1) +endif() + +# ----------------------------------------------------------------------------- +# | OPTIONAL FEATURES | +# ----------------------------------------------------------------------------- + +if(CENTRAL_API) + set(requiredlibs) + find_package(CURL) + if(CURL_FOUND) + include_directories(${CURL_INCLUDE_DIR}) + set(requiredlibs ${requiredlibs} ${CURL_LIBRARIES}) + else(CURL_FOUND) + message( + FATAL_ERROR "Could not find the CURL library and development files.") + endif(CURL_FOUND) endif() # ----------------------------------------------------------------------------- # | JNI | # ----------------------------------------------------------------------------- -if(SDK_JNI OR BUILDING_ANDROID) - message(STATUS "Looking for JNI") +if(ZTS_ENABLE_JAVA OR BUILD_ANDROID) + message(STATUS "Looking for JNI") - if(BUILDING_WIN) - # We are only interested in finding jni.h: we do not care about extended JVM - # functionality or the AWT library. set(JAVA_AWT_LIBRARY NotNeeded) - # set(JAVA_JVM_LIBRARY NotNeeded) set(JAVA_INCLUDE_PATH2 NotNeeded) - # set(JAVA_AWT_INCLUDE_PATH NotNeeded) - set(JAVA_INCLUDE_PATH "C:\\Program Files\\Java\\jdk-10.0.2\\include") - endif() + if(BUILD_WIN) + # We are only interested in finding jni.h: we do not care about extended JVM + # functionality or the AWT library. set(JAVA_AWT_LIBRARY NotNeeded) + # set(JAVA_JVM_LIBRARY NotNeeded) set(JAVA_INCLUDE_PATH2 NotNeeded) + # set(JAVA_AWT_INCLUDE_PATH NotNeeded) + set(JAVA_INCLUDE_PATH "C:\\Program Files\\Java\\jdk-10.0.2\\include") + endif() - set(JAVA_AWT_LIBRARY NotNeeded) - set(JAVA_JVM_LIBRARY NotNeeded) - set(JAVA_INCLUDE_PATH2 NotNeeded) - set(JAVA_AWT_INCLUDE_PATH NotNeeded) - find_package(JNI REQUIRED) + set(JAVA_AWT_LIBRARY NotNeeded) + set(JAVA_JVM_LIBRARY NotNeeded) + set(JAVA_INCLUDE_PATH2 NotNeeded) + set(JAVA_AWT_INCLUDE_PATH NotNeeded) + find_package(JNI REQUIRED) - if(JNI_FOUND) - message(STATUS "JNI_INCLUDE_DIR=${JNI_INCLUDE_DIRS}") - message(STATUS "JNI_LIBRARIES=${JNI_LIBRARIES}") - list(GET JNI_INCLUDE_DIRS 0 JNI_INCLUDE_DIR) - message(STATUS "jni path=${JNI_INCLUDE_DIR}") - include_directories("${JNI_INCLUDE_DIR}") - # include_directories ("${JNI_INCLUDE_DIRS}") - if(BUILDING_WIN) - include_directories("${JNI_INCLUDE_DIR}\\win32") - endif() - if(BUILDING_MACOS) - include_directories("${JNI_INCLUDE_DIR}/darwin") - endif() - if(BUILDING_LINUX) - include_directories("${JNI_INCLUDE_DIR}/linux") - endif() - else() - message(STATUS "JNI not found") - endif() - if(JNI_FOUND) - add_definitions(-DSDK_JNI=1) - endif() -endif() # SDK_JNI + if(JNI_FOUND) + message(STATUS "JNI_INCLUDE_DIR=${JNI_INCLUDE_DIRS}") + message(STATUS "JNI_LIBRARIES=${JNI_LIBRARIES}") + list(GET JNI_INCLUDE_DIRS 0 JNI_INCLUDE_DIR) + message(STATUS "jni path=${JNI_INCLUDE_DIR}") + include_directories("${JNI_INCLUDE_DIR}") + # include_directories ("${JNI_INCLUDE_DIRS}") + if(BUILD_WIN) + include_directories("${JNI_INCLUDE_DIR}\\win32") + endif() + if(BUILD_MACOS) + include_directories("${JNI_INCLUDE_DIR}/darwin") + endif() + if(BUILD_LINUX) + include_directories("${JNI_INCLUDE_DIR}/linux") + endif() + else() + message(STATUS "JNI not found") + endif() + if(JNI_FOUND) + add_definitions(-DZTS_ENABLE_JAVA=1) + endif() +endif() # ZTS_ENABLE_JAVA # ----------------------------------------------------------------------------- # | SOURCES | # ----------------------------------------------------------------------------- -set(PROJ_DIR ${PROJECT_SOURCE_DIR}) -set(LWIP_SRC_DIR "${PROJ_DIR}/ext/lwip/src") -set(ZTO_SRC_DIR "${PROJ_DIR}/ext/ZeroTierOne") -set(LIBZT_SRC_DIR "${PROJ_DIR}/src") - file(GLOB ztcoreSrcGlob ${ZTO_SRC_DIR}/node/*.cpp - ${ZTO_SRC_DIR}/osdep/OSUtils.cpp ${ZTO_SRC_DIR}/osdep/PortMapper.cpp - ${ZTO_SRC_DIR}/osdep/ManagedRoute.cpp) + ${ZTO_SRC_DIR}/osdep/OSUtils.cpp ${ZTO_SRC_DIR}/osdep/PortMapper.cpp + ${ZTO_SRC_DIR}/osdep/ManagedRoute.cpp) file(GLOB libnatpmpSrcGlob ${ZTO_SRC_DIR}/ext/libnatpmp/natpmp.c - ${ZTO_SRC_DIR}/ext/libnatpmp/wingettimeofday.c - ${ZTO_SRC_DIR}/ext/libnatpmp/getgateway.c) + ${ZTO_SRC_DIR}/ext/libnatpmp/wingettimeofday.c + ${ZTO_SRC_DIR}/ext/libnatpmp/getgateway.c) +if(NOT BUILD_WIN) +list(REMOVE_ITEM libnatpmpSrcGlob ${ZTO_SRC_DIR}/ext/libnatpmp/wingettimeofday.c) +endif() file( - GLOB - libminiupnpcSrcGlob - ${ZTO_SRC_DIR}/ext/miniupnpc/connecthostport.c - ${ZTO_SRC_DIR}/ext/miniupnpc/igd_desc_parse.c - ${ZTO_SRC_DIR}/ext/miniupnpc/minisoap.c - ${ZTO_SRC_DIR}/ext/miniupnpc/minissdpc.c - ${ZTO_SRC_DIR}/ext/miniupnpc/miniupnpc.c - ${ZTO_SRC_DIR}/ext/miniupnpc/miniwget.c - ${ZTO_SRC_DIR}/ext/miniupnpc/minixml.c - ${ZTO_SRC_DIR}/ext/miniupnpc/portlistingparse.c - ${ZTO_SRC_DIR}/ext/miniupnpc/receivedata.c - ${ZTO_SRC_DIR}/ext/miniupnpc/upnpcommands.c - ${ZTO_SRC_DIR}/ext/miniupnpc/upnpdev.c - ${ZTO_SRC_DIR}/ext/miniupnpc/upnperrors.c - ${ZTO_SRC_DIR}/ext/miniupnpc/upnpreplyparse.c) - -if(ZTS_PINVOKE) - set(ZTS_SWIG_WRAPPER_FILE ${LIBZT_SRC_DIR}/bindings/csharp/*.cxx) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DZTS_PINVOKE=1") -endif() + GLOB + libminiupnpcSrcGlob + ${ZTO_SRC_DIR}/ext/miniupnpc/connecthostport.c + ${ZTO_SRC_DIR}/ext/miniupnpc/igd_desc_parse.c + ${ZTO_SRC_DIR}/ext/miniupnpc/minisoap.c + ${ZTO_SRC_DIR}/ext/miniupnpc/minissdpc.c + ${ZTO_SRC_DIR}/ext/miniupnpc/miniupnpc.c + ${ZTO_SRC_DIR}/ext/miniupnpc/miniwget.c + ${ZTO_SRC_DIR}/ext/miniupnpc/minixml.c + ${ZTO_SRC_DIR}/ext/miniupnpc/portlistingparse.c + ${ZTO_SRC_DIR}/ext/miniupnpc/receivedata.c + ${ZTO_SRC_DIR}/ext/miniupnpc/upnpcommands.c + ${ZTO_SRC_DIR}/ext/miniupnpc/upnpdev.c + ${ZTO_SRC_DIR}/ext/miniupnpc/upnperrors.c + ${ZTO_SRC_DIR}/ext/miniupnpc/upnpreplyparse.c) file(GLOB libztSrcGlob ${LIBZT_SRC_DIR}/*.cpp ${ZTS_SWIG_WRAPPER_FILE}) -if(UNIX) - set(LWIP_PORT_DIR ${PROJ_DIR}/ext/lwip-contrib/ports/unix/port) -endif() - -if(BUILDING_WIN) - set(LWIP_PORT_DIR ${PROJ_DIR}/ext/lwip-contrib/ports/win32) -endif() - file( - GLOB - lwipSrcGlob - ${LWIP_SRC_DIR}/netif/*.c - ${LWIP_SRC_DIR}/api/*.c - ${LWIP_PORT_DIR}/sys_arch.c - ${LWIP_SRC_DIR}/core/*.c - ${LWIP_SRC_DIR}/core/ipv4/*.c - ${LWIP_SRC_DIR}/core/ipv6/*.c) + GLOB + lwipSrcGlob + ${LWIP_SRC_DIR}/netif/*.c + ${LWIP_SRC_DIR}/api/*.c + ${LWIP_PORT_DIR}/sys_arch.c + ${LWIP_SRC_DIR}/core/*.c + ${LWIP_SRC_DIR}/core/ipv4/*.c + ${LWIP_SRC_DIR}/core/ipv6/*.c) list(REMOVE_ITEM lwipSrcGlob ${LWIP_SRC_DIR}/netif/slipif.c) # header globs for xcode frameworks file(GLOB frameworkPublicHeaderGlob include/ZeroTierSockets.h) file(GLOB frameworkHeaderGlob ${frameworkPublicHeaderGlob} - ${frameworkPrivateHeaderGlob}) - -# ----------------------------------------------------------------------------- -# | INCLUDES | -# ----------------------------------------------------------------------------- - -include_directories(${ZTO_SRC_DIR}) -include_directories(${ZTO_SRC_DIR}/node) -include_directories(${ZTO_SRC_DIR}/osdep) -include_directories(${ZTO_SRC_DIR}/include) -include_directories(${ZTO_SRC_DIR}/ext/miniupnpc) -include_directories(${ZTO_SRC_DIR}/ext/libnatpmp) -include_directories(${PROJ_DIR}/src) -include_directories(${PROJ_DIR}/include) -include_directories(${LWIP_SRC_DIR}/include) -include_directories(${LWIP_PORT_DIR}/include) -include_directories(${PROJ_DIR}/ext/concurrentqueue) - -# TODO: Should separate this into its own ios.cmake file - -if(IOS_FRAMEWORK) - # Controllers probably won't be run from iPhones, so we can omit JSON support - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DOMIT_JSON_SUPPORT=1") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DOMIT_JSON_SUPPORT=1") - set(DEVROOT - "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer" - ) - if(IOS_ARM64) - set(CMAKE_OSX_ARCHITECTURES arm64) - set(SDKVER "11.4") - endif() - if(IOS_ARMV7) - # armv7 armv7s - set(CMAKE_OSX_ARCHITECTURES "$(ARCHS_STANDARD_32_BIT)") - set(SDKVER "10.0") - endif() - - set(SDKROOT "${DEVROOT}/SDKs/iPhoneOS${SDKVER}.sdk") - if(EXISTS ${SDKROOT}) - set(CMAKE_OSX_SYSROOT "${SDKROOT}") - else() - message("Warning, iOS Base SDK path not found: " ${SDKROOT}) - endif() -endif() - -if(MACOS_FRAMEWORK) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DOMIT_JSON_SUPPORT=1") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DOMIT_JSON_SUPPORT=1") - set(CMAKE_XCODE_ATTRIBUTE_ARCHS "$(ARCHS_STANDARD)") - set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO) - include_directories( - "/Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk/usr/include/") -endif() + ${frameworkPrivateHeaderGlob}) # ----------------------------------------------------------------------------- # | OBJECT LIBRARIES (INTERMEDIATE) | # ----------------------------------------------------------------------------- -# zto_obj -add_library(zto_obj OBJECT ${ztcoreSrcGlob}) -set_target_properties(zto_obj PROPERTIES COMPILE_FLAGS "${ZT_FLAGS}") -if(BUILDING_WIN) - target_link_libraries(zto_obj ws2_32) - target_link_libraries(zto_obj ${shlwapi_LIBRARY_PATH}) - target_link_libraries(zto_obj ${iphlpapi_LIBRARY_PATH}) +if(BUILD_STATIC_LIB) + # zto_obj + add_library(zto_obj OBJECT ${ztcoreSrcGlob}) + set_target_properties(zto_obj PROPERTIES COMPILE_FLAGS "${ZT_FLAGS}") + if(BUILD_WIN) + target_link_libraries(zto_obj ws2_32) + target_link_libraries(zto_obj ${shlwapi_LIBRARY_PATH}) + target_link_libraries(zto_obj ${iphlpapi_LIBRARY_PATH}) + endif() + + # libnatpmp_obj + add_library(libnatpmp_obj OBJECT ${libnatpmpSrcGlob}) + set_target_properties(libnatpmp_obj PROPERTIES COMPILE_FLAGS "-DNATPMP_EXPORTS") + + # miniupnpc_obj + add_library(miniupnpc_obj OBJECT ${libminiupnpcSrcGlob}) + target_compile_definitions( + miniupnpc_obj + PRIVATE ZT_USE_MINIUPNPC + MINIUPNP_STATICLIB + _DARWIN_C_SOURCE + MINIUPNPC_SET_SOCKET_TIMEOUT + MINIUPNPC_GET_SRC_ADDR + _BSD_SOURCE + _DEFAULT_SOURCE + MINIUPNPC_VERSION_STRING=\"2.0\" + UPNP_VERSION_STRING=\"UPnP/1.1\" + ENABLE_STRNATPMPERR + OS_STRING=\"Darwin/15.0.0\") + if(BUILD_DARWIN AND NOT BUILD_IOS_FRAMEWORK) + target_compile_definitions(miniupnpc_obj PRIVATE MACOSX) + endif() + + # lwip_obj + add_library(lwip_obj OBJECT ${lwipSrcGlob}) + set_target_properties(lwip_obj PROPERTIES COMPILE_FLAGS "${LWIP_FLAGS}") + + # libzt_obj + add_library(libzt_obj OBJECT ${libztSrcGlob}) + set_target_properties(libzt_obj PROPERTIES COMPILE_FLAGS "${ZT_FLAGS}") endif() -# libnatpmp_obj -add_library(libnatpmp_obj OBJECT ${libnatpmpSrcGlob}) -set_target_properties(libnatpmp_obj PROPERTIES COMPILE_FLAGS "-DNATPMP_EXPORTS") - -# miniupnpc_obj -add_library(miniupnpc_obj OBJECT ${libminiupnpcSrcGlob}) -target_compile_definitions( - miniupnpc_obj - PRIVATE ZT_USE_MINIUPNPC - MINIUPNP_STATICLIB - _DARWIN_C_SOURCE - MINIUPNPC_SET_SOCKET_TIMEOUT - MINIUPNPC_GET_SRC_ADDR - _BSD_SOURCE - _DEFAULT_SOURCE - MINIUPNPC_VERSION_STRING=\"2.0\" - UPNP_VERSION_STRING=\"UPnP/1.1\" - ENABLE_STRNATPMPERR - OS_STRING=\"Darwin/15.0.0\") -if(BUILDING_DARWIN AND NOT IOS_FRAMEWORK) - target_compile_definitions(miniupnpc_obj PRIVATE MACOSX) -endif() - -# lwip_obj -add_library(lwip_obj OBJECT ${lwipSrcGlob}) -set_target_properties(lwip_obj PROPERTIES COMPILE_FLAGS "${LWIP_FLAGS}") - -# libzt_obj -add_library(libzt_obj OBJECT ${libztSrcGlob}) -set_target_properties(libzt_obj PROPERTIES COMPILE_FLAGS "${ZT_FLAGS}") - # PIC # zto_pic add_library(zto_pic ${ztcoreSrcGlob}) set_target_properties(zto_pic PROPERTIES COMPILE_FLAGS "${ZT_FLAGS}" - POSITION_INDEPENDENT_CODE ON) + POSITION_INDEPENDENT_CODE ON) # libnatpmp_pic add_library(natpmp_pic ${libnatpmpSrcGlob}) set_target_properties(natpmp_pic PROPERTIES COMPILE_FLAGS "-DNATPMP_EXPORTS" - POSITION_INDEPENDENT_CODE ON) + POSITION_INDEPENDENT_CODE ON) # miniupnpc_pic add_library(miniupnpc_pic ${libminiupnpcSrcGlob}) target_compile_definitions( - miniupnpc_pic - PRIVATE MACOSX - ZT_USE_MINIUPNPC - MINIUPNP_STATICLIB - _DARWIN_C_SOURCE - MINIUPNPC_SET_SOCKET_TIMEOUT - MINIUPNPC_GET_SRC_ADDR - _BSD_SOURCE - _DEFAULT_SOURCE - MINIUPNPC_VERSION_STRING=\"2.0\" - UPNP_VERSION_STRING=\"UPnP/1.1\" - ENABLE_STRNATPMPERR - OS_STRING=\"Darwin/15.0.0\") + miniupnpc_pic + PRIVATE MACOSX + ZT_USE_MINIUPNPC + MINIUPNP_STATICLIB + _DARWIN_C_SOURCE + MINIUPNPC_SET_SOCKET_TIMEOUT + MINIUPNPC_GET_SRC_ADDR + _BSD_SOURCE + _DEFAULT_SOURCE + MINIUPNPC_VERSION_STRING=\"2.0\" + UPNP_VERSION_STRING=\"UPnP/1.1\" + ENABLE_STRNATPMPERR + OS_STRING=\"Darwin/15.0.0\") set_target_properties(miniupnpc_pic PROPERTIES POSITION_INDEPENDENT_CODE ON) # lwip_pic @@ -447,164 +564,156 @@ set_target_properties(lwip_pic PROPERTIES COMPILE_FLAGS "${LWIP_FLAGS}") # libzt_pic add_library(zt_pic ${libztSrcGlob}) set_target_properties(zt_pic PROPERTIES COMPILE_FLAGS "${ZT_FLAGS}" - POSITION_INDEPENDENT_CODE ON) + POSITION_INDEPENDENT_CODE ON) + +#set_property( +# TARGET lwip_pic +# APPEND +# PROPERTY STATIC_LIBRARY_FLAGS "-no_warning_for_no_symbols" +#) # ----------------------------------------------------------------------------- -# | BUILD TARGETS (FINAL PRODUCT) | +# | STATIC LIB | # ----------------------------------------------------------------------------- -# libztcore.a -add_library(ztcore STATIC $) -set_target_properties( - ztcore PROPERTIES OUTPUT_NAME ztcore LIBRARY_OUTPUT_DIRECTORY - ${INTERMEDIATE_LIBRARY_OUTPUT_PATH}) +if(BUILD_STATIC_LIB) + # libztcore.a + add_library(ztcore STATIC $) + set_target_properties( + ztcore PROPERTIES OUTPUT_NAME ztcore LIBRARY_OUTPUT_DIRECTORY + ${INTERMEDIATE_LIBRARY_OUTPUT_PATH}) + # libzt.a + add_library( + ${STATIC_LIB_NAME} STATIC + $ $ + $ $ + $ ${libztSrcGlob}) + set_target_properties( + ${STATIC_LIB_NAME} + PROPERTIES LINKER_LANGUAGE CXX + OUTPUT_NAME zt + POSITION_INDEPENDENT_CODE ON + LIBRARY_OUTPUT_DIRECTORY ${INTERMEDIATE_LIBRARY_OUTPUT_PATH}) + set_target_properties(${STATIC_LIB_NAME} PROPERTIES COMPILE_FLAGS "${ZT_FLAGS}") + target_link_libraries(${STATIC_LIB_NAME} ${CMAKE_THREAD_LIBS_INIT}) -# libzt.a -add_library( - ${STATIC_LIB_NAME} STATIC - $ $ - $ $ - $ ${libztSrcGlob}) -set_target_properties( - ${STATIC_LIB_NAME} - PROPERTIES OUTPUT_NAME zt - POSITION_INDEPENDENT_CODE ON - LIBRARY_OUTPUT_DIRECTORY ${INTERMEDIATE_LIBRARY_OUTPUT_PATH}) -set_target_properties(${STATIC_LIB_NAME} PROPERTIES COMPILE_FLAGS "${ZT_FLAGS}") -target_link_libraries(${STATIC_LIB_NAME} ${CMAKE_THREAD_LIBS_INIT}) + if(BUILD_WIN) + target_link_libraries(${STATIC_LIB_NAME} ${ws2_32_LIBRARY_PATH} + ${shlwapi_LIBRARY_PATH} ${iphlpapi_LIBRARY_PATH}) + endif() # BUILD_STATIC_LIB +endif() if(CENTRAL_API) - target_link_libraries(${STATIC_LIB_NAME} ${CURL_LIBRARIES}) +# target_link_libraries(${STATIC_LIB_NAME} ${CURL_LIBRARIES}) endif() -if(BUILDING_WIN) - target_link_libraries(${STATIC_LIB_NAME} ${ws2_32_LIBRARY_PATH} - ${shlwapi_LIBRARY_PATH} ${iphlpapi_LIBRARY_PATH}) -endif() -# libzt.so/dylib/dll -add_library(${DYNAMIC_LIB_NAME} SHARED ${libztSrcGlob}) -target_link_libraries(${DYNAMIC_LIB_NAME} zt_pic lwip_pic zto_pic natpmp_pic - miniupnpc_pic) -set_target_properties(${DYNAMIC_LIB_NAME} PROPERTIES COMPILE_FLAGS - "${ZT_FLAGS}") -set_target_properties( - ${DYNAMIC_LIB_NAME} PROPERTIES OUTPUT_NAME ${DYNAMIC_LIB_OUTPUT_NAME} - WINDOWS_EXPORT_ALL_SYMBOLS true) -target_link_libraries( - ${DYNAMIC_LIB_NAME} - ${CMAKE_THREAD_LIBS_INIT} - ${ws2_32_LIBRARY_PATH} - ${shlwapi_LIBRARY_PATH} - ${iphlpapi_LIBRARY_PATH} - zt_pic - lwip_pic - zto_pic - natpmp_pic - miniupnpc_pic) -if(CENTRAL_API) - target_link_libraries(${DYNAMIC_LIB_NAME} ${CURL_LIBRARIES}) -endif() +# ----------------------------------------------------------------------------- +# | SHARED LIB | +# ----------------------------------------------------------------------------- -set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +if(BUILD_SHARED_LIB) + # libzt.so/dylib/dll + add_library(${DYNAMIC_LIB_NAME} SHARED ${libztSrcGlob}) + target_link_libraries(${DYNAMIC_LIB_NAME} ${PYTHON_LIBRARIES}) + target_link_libraries(${DYNAMIC_LIB_NAME} zt_pic lwip_pic zto_pic natpmp_pic + miniupnpc_pic) + set_target_properties(${DYNAMIC_LIB_NAME} + PROPERTIES COMPILE_FLAGS + "${ZT_FLAGS}") + set_target_properties( + ${DYNAMIC_LIB_NAME} PROPERTIES OUTPUT_NAME ${DYNAMIC_LIB_OUTPUT_NAME} + WINDOWS_EXPORT_ALL_SYMBOLS true) + target_link_libraries( + ${DYNAMIC_LIB_NAME} + ${CMAKE_THREAD_LIBS_INIT} + ${ws2_32_LIBRARY_PATH} + ${shlwapi_LIBRARY_PATH} + ${iphlpapi_LIBRARY_PATH} + zt_pic + lwip_pic + zto_pic + natpmp_pic + miniupnpc_pic) -if(BUILDING_ANDROID) - target_link_libraries(${DYNAMIC_LIB_NAME} android log) -endif() + if(CENTRAL_API) + target_link_libraries(${DYNAMIC_LIB_NAME} ${CURL_LIBRARIES}) + endif() + + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + + if(BUILD_ANDROID) + target_link_libraries(${DYNAMIC_LIB_NAME} android log) + endif() +endif() # BUILD_SHARED_LIB # xcode framework if(IN_XCODE) - # stdint.h is needed for API definitions but can cause conflicts in certain - # projects where integers may already be defined. Thus this flag is used to - # exclude their definition in ZeroTierSockets.h - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DZTS_NO_STDINT_H=1") + # stdint.h is needed for API definitions but can cause conflicts in certain + # projects where integers may already be defined. Thus this flag is used to + # exclude their definition in ZeroTierSockets.h + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DZTS_NO_STDINT_H=1") - include_directories(${frameworkHeaderGlob}) + include_directories(${frameworkHeaderGlob}) - add_library( - ${XCODE_FRAMEWORK_NAME} STATIC - $ - $ - $ - $ - $ - ${libztSrcGlob} - ${frameworkHeaderGlob}) + add_library( + ${XCODE_FRAMEWORK_NAME} STATIC + $ + $ + $ + $ + $ + ${libztSrcGlob} + ${frameworkHeaderGlob}) - set_target_properties(${XCODE_FRAMEWORK_NAME} PROPERTIES ENABLE_BITCODE "YES") - set_target_properties(${XCODE_FRAMEWORK_NAME} - PROPERTIES BITCODE_GENERATION_MODE bitcode) - target_compile_options(${XCODE_FRAMEWORK_NAME} PUBLIC -fembed-bitcode) - target_link_libraries(${XCODE_FRAMEWORK_NAME} PUBLIC -fembed-bitcode) + set_target_properties(${XCODE_FRAMEWORK_NAME} PROPERTIES ENABLE_BITCODE "YES") + set_target_properties(${XCODE_FRAMEWORK_NAME} + PROPERTIES BITCODE_GENERATION_MODE bitcode) + target_compile_options(${XCODE_FRAMEWORK_NAME} PUBLIC -fembed-bitcode) + target_link_libraries(${XCODE_FRAMEWORK_NAME} PUBLIC -fembed-bitcode) - set_target_properties( - ${XCODE_FRAMEWORK_NAME} - PROPERTIES - FRAMEWORK YES - FRAMEWORK_VERSION A - XCODE_ATTRIBUTE_DEFINES_MODULE YES - MACOSX_FRAMEWORK_IDENTIFIER com.cmake.${XCODE_FRAMEWORK_NAME} - XCODE_ATTRIBUTE_MODULEMAP_FILE "${PROJ_DIR}/ports/module.modulemap" - PUBLIC_HEADER "${frameworkHeaderGlob}" - XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer" - XCODE_ATTRIBUTE_CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES YES) + set_target_properties( + ${XCODE_FRAMEWORK_NAME} + PROPERTIES + FRAMEWORK YES + FRAMEWORK_VERSION A + XCODE_ATTRIBUTE_DEFINES_MODULE YES + MACOSX_FRAMEWORK_IDENTIFIER com.cmake.${XCODE_FRAMEWORK_NAME} + XCODE_ATTRIBUTE_MODULEMAP_FILE "${PROJ_DIR}/pkg/apple/module.modulemap" + PUBLIC_HEADER "${frameworkHeaderGlob}" + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer" + XCODE_ATTRIBUTE_CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES YES) endif() # ----------------------------------------------------------------------------- -# | EXECUTABLES | +# | SELFTEST | # ----------------------------------------------------------------------------- -if(SHOULD_BUILD_TESTS) - add_executable(earthtest ${PROJ_DIR}/examples/cpp/earthtest.cpp) - target_link_libraries(earthtest ${STATIC_LIB_NAME}) - add_executable(adhoc ${PROJ_DIR}/examples/cpp/adhoc.cpp) - target_link_libraries(adhoc ${STATIC_LIB_NAME}) - add_executable(comprehensive ${PROJ_DIR}/examples/cpp/comprehensive.cpp) - target_link_libraries(comprehensive ${STATIC_LIB_NAME}) - add_executable(client ${PROJ_DIR}/examples/cpp/client.cpp) - target_link_libraries(client ${STATIC_LIB_NAME}) - add_executable(server ${PROJ_DIR}/examples/cpp/server.cpp) - target_link_libraries(server ${STATIC_LIB_NAME}) - add_executable(nonblockingclient - ${PROJ_DIR}/examples/cpp/nonblockingclient.cpp) - target_link_libraries(nonblockingclient ${STATIC_LIB_NAME}) - add_executable(nonblockingserver - ${PROJ_DIR}/examples/cpp/nonblockingserver.cpp) - target_link_libraries(nonblockingserver ${STATIC_LIB_NAME}) - add_executable(keymanagement ${PROJ_DIR}/examples/cpp/keymanagement.cpp) - target_link_libraries(keymanagement ${STATIC_LIB_NAME}) - if(CENTRAL_API) - add_executable(centralapi ${PROJ_DIR}/examples/cpp/centralapi.cpp) - target_link_libraries(centralapi ${STATIC_LIB_NAME}) - endif() +if(BUILD_HOST_SELFTEST) + add_executable(selftest ${PROJ_DIR}/test/selftest.cpp) + target_link_libraries(selftest ${STATIC_LIB_NAME}) + project(TEST) + enable_testing() + add_test(NAME selftest COMMAND selftest) endif() # ----------------------------------------------------------------------------- # | INSTALL | # ----------------------------------------------------------------------------- -set(PUBLIC_ZT_HEADERS ${PROJECT_SOURCE_DIR}/include/ZeroTierSockets.h) - -set_target_properties(${STATIC_LIB_NAME} PROPERTIES PUBLIC_HEADER - "${PUBLIC_ZT_HEADERS}") -install( - TARGETS ${STATIC_LIB_NAME} - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - PUBLIC_HEADER DESTINATION include) -install( - TARGETS ${DYNAMIC_LIB_NAME} - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib) - -# ----------------------------------------------------------------------------- -# | CI TESTS | -# ----------------------------------------------------------------------------- - -add_executable(errortest ${PROJ_DIR}/test/error.cpp) -target_link_libraries(errortest ${STATIC_LIB_NAME}) -project(TEST) -enable_testing() -add_test(NAME MyTest COMMAND errortest) +if(INSTALLABLE) + set(PUBLIC_ZT_HEADERS ${PROJECT_SOURCE_DIR}/include/ZeroTierSockets.h) + set_target_properties(${STATIC_LIB_NAME} PROPERTIES PUBLIC_HEADER + "${PUBLIC_ZT_HEADERS}") + install( + TARGETS ${STATIC_LIB_NAME} + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + PUBLIC_HEADER DESTINATION include) + install( + TARGETS ${DYNAMIC_LIB_NAME} + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) +endif() \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 0806da5..0000000 --- a/Makefile +++ /dev/null @@ -1,127 +0,0 @@ -DIST_BUILD_SCRIPT := ./dist.sh - -#EXECUTABLES = cmake -#build_reqs := $(foreach exec,$(EXECUTABLES),\ -# $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH"))) - -.PHONY: list -list: - @$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= \ - -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") \ - {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | xargs - -# Pull all submodules -update: - @git submodule update --init - @git submodule status - -# Patch submodules (issue update first) -patch: - #-cd ext/lwip; git apply ../lwip.patch; - #-cd ext/lwip-contrib; git apply ../lwip-contrib.patch; - #-cd ext/ZeroTierOne; git apply ../ZeroTierOne.patch; - -# Target-specific clean -clean_ios: - -rm -rf ports/xcode_ios - -rm -rf ports/xcode_ios_simulator -clean_macos: - -rm -rf ports/xcode_macos -clean_android: - -rm -rf pkg/android/app/build - -find pkg -name ".externalNativeBuild" -exec rm -r "{}" \; - $(DIST_BUILD_SCRIPT) android "clean" -clean_products: - -rm -rf products -.PHONY: clean -clean: clean_ios clean_macos clean_android - $(DIST_BUILD_SCRIPT) clean - -# Use CMake generators to build projects from CMakeLists.txt -projects: - $(DIST_BUILD_SCRIPT) generate_projects - -# Android -android_debug: - $(DIST_BUILD_SCRIPT) android "debug" - $(DIST_BUILD_SCRIPT) clean_android_project - $(DIST_BUILD_SCRIPT) prep_android_example "debug" -android_release: - $(DIST_BUILD_SCRIPT) android "release" - $(DIST_BUILD_SCRIPT) clean_android_project - $(DIST_BUILD_SCRIPT) prep_android_example "release" -android: android_debug android_release -prep_android_debug_example: - $(DIST_BUILD_SCRIPT) prep_android_example "debug" -prep_android_release_example: - $(DIST_BUILD_SCRIPT) prep_android_example "release" - -# macOS -macos_debug: - $(DIST_BUILD_SCRIPT) macos "debug" -macos_release: - $(DIST_BUILD_SCRIPT) macos "release" -macos: macos_debug macos_release - -# xcframework -xcframework: - xcodebuild -project ports/apple/zt.xcodeproj archive -scheme zt -sdk macosx -archivePath build/macosx - xcodebuild -project ports/apple/zt.xcodeproj archive -scheme zt -sdk iphoneos -archivePath build/iphoneos - xcodebuild -project ports/apple/zt.xcodeproj archive -scheme zt -sdk iphonesimulator -archivePath build/iphonesimulator - - xcodebuild -create-xcframework \ - -framework build/macosx.xcarchive/Products/Library/Frameworks/zt.framework \ - -framework build/iphoneos.xcarchive/Products/Library/Frameworks/zt.framework \ - -framework build/iphonesimulator.xcarchive/Products/Library/Frameworks/zt.framework \ - -output lib/zt.xcframework - - rm -rf build/macosx.xcarchive - rm -rf build/iphoneos.xcarchive - rm -rf build/iphonesimulator.xcarchive - -# iOS -ios_debug: - $(DIST_BUILD_SCRIPT) ios "debug" -ios_release: - $(DIST_BUILD_SCRIPT) ios "release" -ios: ios_debug ios_release - -# Host -host_release: - $(DIST_BUILD_SCRIPT) host "release" -host_debug: - $(DIST_BUILD_SCRIPT) host "debug" -host_clean: - $(DIST_BUILD_SCRIPT) host "clean" -host_jar_debug: - $(DIST_BUILD_SCRIPT) host_jar "debug" -host_jar_release: - $(DIST_BUILD_SCRIPT) host_jar "release" -host_jar: host_jar_debug host_jar_release - -host_pinvoke_release: - $(DIST_BUILD_SCRIPT) host_pinvoke "release" -host_pinvoke_debug: - $(DIST_BUILD_SCRIPT) host_pinvoke "debug" -host_pinvoke: host_pinvoke_release host_pinvoke_debug - -host: host_debug host_release - -# Build every target available on this host -all: host host_pinvoke host_jar macos ios android - $(DIST_BUILD_SCRIPT) display - -# [For distribution process only] Prepare remote builds -wrap: - $(DIST_BUILD_SCRIPT) wrap - -# Binary distribution -bdist: - $(DIST_BUILD_SCRIPT) merge - $(DIST_BUILD_SCRIPT) bdist - -# Source distribution -sdist: update patch - $(DIST_BUILD_SCRIPT) sdist - -dist: bdist sdist diff --git a/README b/README new file mode 100644 index 0000000..8a9bca1 --- /dev/null +++ b/README @@ -0,0 +1,23 @@ + + +-------------------------------------------------------------------------------- + libzt --- Encrypted P2P SD-WAN library by ZeroTier +-------------------------------------------------------------------------------- + +This project uses cmake as a build system generator. The scripts build.sh and +build.ps1 are used to simplify building and packaging for various targets. + + dist/ : Finished targets: libraries, binaries, packages, etc. + cache/ : Build system caches that can safely be deleted after build. + pkg/ : Project, script and spec files to generate packages. + +1. git submodule update --init +2. (*)nix : ./build.sh host "release" +2. Windows : . .\build.ps1; Build-Host -BuildType "Release" + +To see additional build targets: + +./build.sh list + +Help, bugs, and feature requests: https://github.com/zerotier/libzt/issues + diff --git a/README.md b/README.md index 92a027d..e858668 100644 --- a/README.md +++ b/README.md @@ -1,390 +1,108 @@ -# ZeroTier SDK -Connect physical devices, virtual devices, and application instances as if everything is on a single LAN. -*** +

+

ZeroTier SDK

+Peer-to-peer and cross-platform encrypted connections built right into your app or service. No drivers, no root, and no host configuration. -The ZeroTier SDK brings your network into user-space. We've paired our network hypervisor core with a network stack ([lwIP](https://savannah.nongnu.org/projects/lwip/)) to provide your application with an exclusive and private virtual network interface. All traffic on this interface is end-to-end encrypted between each peer and we provide an easy-to-use socket interface derived from [Berkeley Sockets](https://en.wikipedia.org/wiki/Berkeley_sockets). Since we aren't using the kernel's network stack that means, no drivers, no root, and no host configuration requirements. For a more in-depth discussion on the technical side of ZeroTier, check out our [Manual](https://www.zerotier.com/manual.shtml). For troubleshooting advice see our [Knowledgebase](https://zerotier.atlassian.net/wiki/spaces/SD/overview). If you need further assistance, create an account at [my.zerotier.com](https://my.zerotier.com) and join our community of users and professionals. +
-Downloads: [download.zerotier.com/dist/sdk](https://download.zerotier.com/dist/sdk) +
Examples | +API Documentation | +Community | +FAQ | +Report a Bug -
+
-## Building on Linux, macOS -*Requires [CMake](https://cmake.org/download/), [Clang](https://releases.llvm.org/download.html) is recommended* -``` -git submodule update --init -make host_release CC=clang CXX=clang++ -``` +latest libzt version +Last Commit +Build Status (master branch) +
-## Building on Windows -*Requires [CMake](https://cmake.org/download/) and [PowerShell](https://github.com/powershell/powershell)* +| Language/Platform | Installation | Version | Example | +|:----------|:---------|:---|:---| +| C/C++ | [Build from source](./BUILDING.md) | version|[C/C++](./examples/cpp) | +| Objective-C | See [examples/objective-c](./examples/objective-c) | version |[Objective-C](./examples/objective-c) | +| C# | `Install-Package ZeroTier.Sockets` | |[C#](./examples/csharp) | +| Python | `pip install libzt`| |[Python](./examples/python) | +| Rust | Coming Soon | version|[Rust](./examples/rust) | +| Swift | See [examples/swift](./examples/swift) |version |[Swift](./examples/swift) | +| Java | See [examples/java](./examples/java) | |[Java](./examples/java) | +| Node.js | See [examples/nodejs](./examples/nodejs) ||[Node.js](./examples/nodejs) | +| Linux | [Build from source](#build-from-source) | version| [C/C++](./examples/cpp) | +| macOS | `brew install libzt`| | [C/C++](./examples/cpp), [Objective-C](./examples/objective-c) | +| iOS / iPadOS | [zt.framework]() | | [Objective-C](./examples/objective-c), [Swift](./examples/swift) | +| Android | [zt.aar]() | | [Java](./examples/java) | -``` -git submodule update --init -. ./dist.ps1 -Build-Library -BuildType "Release" -Arch "Win32|x64|ARM|ARM64" -LanguageBinding "none|csharp" -``` +
-*Note: To build both `release` and `debug` libraries for only your host's architecture use `make host`. Or optionally `make host_release` for release only. To build everything including things like iOS frameworks, Android packages, etc, use `make all`. Possible build targets can be seen by using `make list`. Resultant libraries will be placed in `./lib`, test and example programs will be placed in `./bin`* - -Typical build output: - -``` -lib -├── release -| └── linux-x86_64 -| ├── libzt.a -| └── libzt.so -└── debug - └── ... -bin -└── release - └── linux-x86_64 - ├── client - └── server -``` - -Example linking step: - -``` -clang++ -o yourApp yourApp.cpp -L./lib/release/linux-x86_64/ -lzt; ./yourApp -``` - -
- -## Starting ZeroTier - -The next few sections explain how to use the network control interface portion of the API. These functions are non-blocking and will return an error code specified in the [Error Handling](#error-handling) section and will result in the generation of callback events detailed in the [Event Handling](#event-handling) section. It is your responsibility to handle these events. To start the service, simply call: - -`zts_start(char *path, void (*userCallbackFunc)(struct zts_callback_msg*), int port)` - -At this stage, if a cryptographic identity for this node does not already exist on your local storage medium, it will generate a new one and store it, the node's address (commonly referred to as `nodeId`) will be derived from this identity and will be presented to you upon receiving the `ZTS_EVENT_NODE_ONLINE` shown below. The first argument `path` is a path where you will direct ZeroTier to store its automatically-generated cryptographic identity files (`identity.public` and `identity.secret`), these files are your keys to communicating on the network. Keep them safe and keep them unique. If any two nodes are online using the same identities you will have a bad time. The second argument `userCallbackFunc` is a function that you specify to handle all generated events for the life of your program, see below: +
``` #include "ZeroTierSockets.h" -... - -bool networkReady = false; - -void on_zts_event(struct zts_callback_msg *msg) -{ - if (msg->eventCode == ZTS_EVENT_NODE_ONLINE) { - printf("ZTS_EVENT_NODE_ONLINE, nodeId=%llx\n", msg->node->address); - networkReady = true; - } - ... -} - int main() { - zts_start("configPath", &on_zts_event, 9994); - uint64_t nwid = 0x0123456789abcdef; - while (!networkReady) { sleep(1); } - zts_join(nwid); - int fd = zts_socket(ZTS_AF_INET, ZTS_SOCK_STREAM, 0); + zts_start(...) + zts_join(networkId); + zts_socket(ZTS_AF_INET, ZTS_SOCK_STREAM, 0); + zts_connect(...); ... - return 0; -} - -``` - -For more complete examples see `./examples/` - -
- -After calling `zts_start()` you will receive one or more events specified in the [Node Events](#node-events) section. After receiving `ZTS_EVENT_NODE_ONLINE` you will be allowed to join or leave networks. You must authorize the node ID provided by the this callback event to join your network. This can be done manually or via our [Web API](https://my.zerotier.com/help/api). Note however that if you are using an Ad-hoc network, it has no controller and therefore requires no authorization. - -At the end of your program or when no more network activity is anticipated, the user application can shut down the service with `zts_stop()`. However, it is safe to leave the service running in the background indefinitely as it doesn't consume much memory or CPU while at idle. `zts_stop()` is a non-blocking call and will itself issue a series of events indicating that various aspects of the ZeroTier service have successfully shut down. - -It is worth noting that while `zts_stop()` will stop the service, the user-space network stack will continue operating in a headless hibernation mode. This is intended behavior due to the fact that the network stack we've chosen doesn't currently support the notion of shutdown since it was initially designed for embedded applications that are simply switched off. If you do need a way to shut everything down and free all resources you can call `zts_free()`, but please note that calling this function will prevent all subsequent `zts_start()` calls from succeeding and will require a full application restart if you want to run the service again. The events `ZTS_EVENT_NODE_ONLINE` and `ZTS_EVENT_NODE_OFFLINE` can be seen periodically throughout the lifetime of your application depending on the reliability of your underlying network link, these events are lagging indicators and are typically only triggered every thirty (30) seconds. - -Lastly, the function `zts_restart()` is provided as a way to restart the ZeroTier service along with all of its virtual interfaces. The network stack will remain online and undisturbed during this call. Note that this call will temporarily block until the service has fully shut down, then will return and you may then watch for the appropriate startup callbacks mentioned above. - -
- -## Joining a network - -Joining a ZeroTier virtual network is as easy as calling `zts_join(uint64_t networkId)`. Similarly there is a `zts_leave(uint64_t networkId)`. Note that `zts_start()` must be called and a `ZTS_EVENT_NODE_ONLINE` event must have been received before these calls will succeed. After calling `zts_join()` any one of the events detailed in the [Network Events](#network-events) section may be generated. - -
- -## Connecting and communicating with peers - -Creating a standard socket connection generally works the same as it would using an ordinary socket interface, however with ZeroTier there is a subtle difference in how connections are established which may cause confusion. Since ZeroTier employs transport-triggered link provisioning a direct connection between peers will not exist until contact has been attempted by at least one peer. During this time before a direct link is available traffic will be handled via our free relay service. The provisioning of this direct link usually only takes a couple of seconds but it is important to understand that if you attempt something like s `zts_connect(...)` call during this time it may fail due to packet loss. Therefore it is advised to repeatedly call `zts_connect(...)` until it succeeds and to wait to send additional traffic until `ZTS_EVENT_PEER_DIRECT` has been received for the peer you are attempting to communicate with. All of the above is optional, but it will improve your experience. - -`tl;dr: Try a few times and wait a few seconds` - -As a mitigation for the above behavior, ZeroTier will by default cache details about how to contact a peer in the `peers.d` subdirectory of the config path you passed to `zts_start(...)`. In scenarios where paths do not often change, this can almost completely eliminate the issue and will make connections nearly instantaneous. If however you do not wish to cache these details you can disable it via `zts_set_peer_caching(false)`. - -
- -## Event handling - -As mentioned in previous sections, the control API works by use of non-blocking calls and the generation of a few dozen different event types. Depending on the type of event there may be additional contextual information attached to the `zts_callback_msg` object that you can use. This contextual information will be housed in one of the following structures which are defined in `include/ZeroTierSockets.h`: - -``` -struct zts_callback_msg -{ - int eventCode; - struct zts_node_details *node; - struct zts_network_details *network; - struct zts_netif_details *netif; - struct zts_virtual_network_route *route; - struct zts_peer_details *peer; - struct zts_addr_details *addr; -}; -``` - -Here's an example of a callback function: - -``` -void on_zts_event(struct zts_callback_msg *msg) -{ - if (msg->eventCode == ZTS_EVENT_NODE_ONLINE) { - printf("ZTS_EVENT_NODE_ONLINE, node=%llx\n", msg->node->address); - // You can join networks now! - } } ``` -In this callback function you can perform additional non-blocking API calls or other work. While not returning control to the service isn't forbidden (the event messages are generated by a separate thread) it is recommended that you return control as soon as possible as not returning will prevent the user application from receiving additional callback event messages which may be time-sensitive. - -
- -A typical ordering of messages may look like the following: +# Build from source ``` -... -ZTS_EVENT_NODE_ONLINE // Your node is ready to be used. -ZTS_EVENT_ADDR_ADDED_IP4 // Your node received an IP address assignment on a given network. -ZTS_EVENT_NETWORK_UPDATE // Something about a network changed. -ZTS_EVENT_NETWORK_READY_IP4 // Your node has joined a network, has an address, and can send/receive traffic. -ZTS_EVENT_PEER_RELAY // A peer was discovered but no direct path exists (yet.) -... -ZTS_EVENT_PEER_DIRECT // One or more direct paths to a peer were discovered. +git submodule update --init ``` -## Node Events +This project uses [CMake](https://cmake.org/download/) as a build system generator. The scripts `build.*` simplify building and packaging for various targets. There are many targets and configurations not mentioned here. -Accessible via `msg->node` as a `zts_node_details` object, this message type will contain information about the status of your node. *Possible values of `msg->eventCode`:* +|Platform| Build instructions | Notes | +|:---|:---|:---| +|Linux | `./build.sh host "release"`| [build.sh](./build.sh) | +|macOS | `./build.sh host "release"`| [build.sh](./build.sh) | +|Windows | `. .\build.ps1; Build-Host -BuildType "Release"`| [build.ps1](./build.ps1), *Requires [PowerShell](https://github.com/powershell/powershell)*| + +Using the `host` keyword will automatically detect the current machine type and build standard libzt for use in C/C++ (no additional language bindings.) See `./build.sh list` for additional target options. + +Example output: ``` -ZTS_EVENT_NODE_OFFLINE // Your node is offline. -ZTS_EVENT_NODE_ONLINE // Your node is online and ready to communicate! -ZTS_EVENT_NODE_DOWN // The node is down (for any reason.) -ZTS_EVENT_NODE_IDENTITY_COLLISION // There is another node with the same identity causing a conflict. -ZTS_EVENT_NODE_UNRECOVERABLE_ERROR // Something went wrong internally. -ZTS_EVENT_NODE_NORMAL_TERMINATION // Your node has terminated. +~/libzt/dist/macos-x64-host-release +├── bin +│   ├── client +│   ├── server +│   └── ... +└── lib + ├── libzt.a + └── libzt.dylib ``` -*Example contents of `msg->node`:* +Important directories: -``` -id : f746d550dd -version : 1.4.6 -primaryPort : 9995 -secondaryPort : 0 -``` +|Directory| Purpose| +|:---|:---| +|`dist`| Contains finished targets (libraries, binaries, packages, etc.)| +|`cache`| Contains build system caches that can safely be deleted after use.| +|`pkg`| Contains project, script and spec files to generate packages.| -## Network Events +# Self-hosting (Optional) -Accessible via `msg->network` as a `zts_network_details` object, this message type will contain information about the status of a particular network your node has joined. *Possible values of `msg->eventCode`:* +We provide ways for your app or enterprise to function indepenently from any of our services if desired. -``` -ZTS_EVENT_NETWORK_NOT_FOUND // The network does not exist. The provided networkID may be incorrect. -ZTS_EVENT_NETWORK_CLIENT_TOO_OLD // This client is too old. -ZTS_EVENT_NETWORK_REQ_CONFIG // Waiting for network config, this might take a few seconds. -ZTS_EVENT_NETWORK_OK // Node successfully joined. -ZTS_EVENT_NETWORK_ACCESS_DENIED // The network is private. Your node requires authorization. -ZTS_EVENT_NETWORK_READY_IP4 // Your node successfully received an IPv4 address. -ZTS_EVENT_NETWORK_READY_IP6 // Your node successfully received an IPv6 address. -ZTS_EVENT_NETWORK_DOWN // For some reason the network is no longer available. -ZTS_EVENT_NETWORK_UPDATE // The network's config has changed: mtu, name, managed route, etc. -``` +While we do operate a global network of redundant root servers, network controllers and an admin API/UI called [Central](https://my.zerotier.com), some use-cases require full control over the infrastructure and we try to make it as easy as possible to set up your own controllers and root servers: See [here](https://github.com/zerotier/ZeroTierOne/tree/master/controller) to learn more about how to set up your own network controller, and [here](https://www.zerotier.com/manual/#4_4) to learn more about setting up your own roots. -*Example contents of `msg->network`:* +# Help -``` -nwid : 8bd712bf36bdae5f -mac : ae53fa031fcf -name : cranky_hayes -type : 0 -mtu : 2800 -dhcp : 0 -bridge : 0 -broadcastEnabled : 1 -portError : 0 -netconfRevision : 34 -routeCount : 1 -multicastSubscriptionCount : 1 -- mac=ffffffffffff, adi=ac1b2561 -addresses: -- FC5D:69B6:E0F7:46D5:50DD::1 -- 172.27.37.97 -routes: -- target : 172.27.0.0 -- via : 0.0.0.0 - - flags : 0 - - metric : 0 -``` + - Reference: [C API](./include/README.md) + - Examples: [examples/](./examples) + - Bug reports: [Open a github issue](https://github.com/zerotier/libzt/issues). + - General ZeroTier troubleshooting: [Knowledgebase](https://zerotier.atlassian.net/wiki/spaces/SD/overview). + - Talk to us: + - Community: [discuss.zerotier.com](https://discuss.zerotier.com) + - @zerotier + - r/zerotier -
- -## Peer Events - -Accessible via `msg->peer` as a `zts_peer_details` object, this message type will contain information about a peer that was discovered by your node. These events are triggered when the reachability status of a peer has changed. *Possible values of `msg->eventCode`:* - -``` -ZTS_EVENT_PEER_DIRECT // At least one direct path to this peer is known. -ZTS_EVENT_PEER_RELAY // No direct path to this peer is known. It will be relayed, (high packet loss and jitter.) -ZTS_EVENT_PEER_UNREACHABLE // Peer is not reachable by any means. -ZTS_EVENT_PEER_PATH_DISCOVERED // A new direct path to this peer has been discovered. -ZTS_EVENT_PEER_PATH_DEAD // A direct path to this peer has expired. -``` - -*Example contents of `msg->peer`:* - -``` -peer : a747d5502d -role : 0 -latency : 4 -version : 1.4.6 -pathCount : 2 - - 172.27.37.97 - - F75D:69B6:E0C7:47D5:51DB::1 -``` - -## Address Events - -Accessible via `msg->addr` as a `zts_addr_details` object, this message type will contain information about addresses assign to your node on a particular network. The information contained in these events is also available via `ZTS_EVENT_NETWORK_UPDATE` events. *Possible values of `msg->eventCode`:* - -``` -ZTS_EVENT_ADDR_ADDED_IP4 // A new IPv4 address was assigned to your node on the indicated network. -ZTS_EVENT_ADDR_REMOVED_IP4 // An IPv4 address assignment to your node was removed on the indicated network. -ZTS_EVENT_ADDR_ADDED_IP6 // A new IPv6 address was assigned to your node on the indicated network. -ZTS_EVENT_ADDR_REMOVED_IP6 // An IPv6 address assignment to your node was removed on the indicated network. -``` - -*Example contents of `msg->addr`:* - -``` -nwid : a747d5502d -addr : 172.27.37.97 -``` - -
- -## Error handling - -Calling a `zts_*` function will result in one of the following return codes. Only when `ZTS_ERR` is returned will `zts_errno` be set. Its values closely mirror those used in standard socket interfaces and are defined in `include/ZeroTierSockets.h`. - -``` -ZTS_ERR_OK // No error -ZTS_ERR_SOCKET // Socket error (see zts_errno for more information) -ZTS_ERR_SERVICE // General ZeroTier internal error. Maybe you called something out of order? -ZTS_ERR_ARG // An argument provided is invalid. -ZTS_ERR_NO_RESULT // Call succeeded but no result was available. Not necessarily an error. -ZTS_ERR_GENERAL // General internal failure. Consider filing a bug report. -``` - -*NOTE: For Android/Java (or similar) which use JNI, the socket API's error codes are negative values encoded in the return values of function calls* -*NOTE: For protocol-level errors (such as dropped packets) or internal network stack errors, see the section `Statistics`* - -
- -## Common pitfalls - - - If you have started a node but have not received a `ZTS_EVENT_NODE_ONLINE`: - - You may need to view our [Router Config Tips](https://zerotier.atlassian.net/wiki/spaces/SD/pages/6815768/Router+Configuration+Tips) knowledgebase article. Sometimes this is due to firewall/NAT settings. - - - If you have received a `ZTS_EVENT_NODE_ONLINE` event and attempted to join a network but do not see your node ID in the network panel on [my.zerotier.com](my.zerotier.com) after some time: - - You may have typed in your network ID incorrectly. - - Used an improper integer representation for your network ID (e.g. `int` instead of `uint64_t`). - - - If you are unable to reliably connect to peers: - - You should first read the section on [Connecting and communicating with peers](#connecting-and-communicating-with-peers). - - If the previous step doesn't help move onto our knowledgebase article [Router Config Tips](https://zerotier.atlassian.net/wiki/spaces/SD/pages/6815768/Router+Configuration+Tips). Sometimes this can be a transport-triggered link issue, and sometimes it can be a firewall/NAT issue. - - - API calls seem to fail in nonsensical ways and you're tearing your hair out: - - Be sure to read and understand the [API compatibility with host OS](#api-compatibility-with-host-os) section. - - See the [Debugging](#debugging) section for more advice. - -
- -## API compatibility with host OS - -Since libzt re-implements a socket interface likely very similar to your host OS's own interface it may be tempting to mix and match host OS structures and functions with those of libzt. This may work on occasion, but you are tempting fate. Here are a few important guidelines: - -If you are calling a `zts_*` function, use the appropriate `ZTS_*` constants: -``` -zts_socket(ZTS_AF_INET6, ZTS_SOCK_DGRAM, 0); (CORRECT) -zts_socket(AF_INET6, SOCK_DGRAM, 0); (INCORRECT) -``` - -If you are calling a `zts_*` function, use the appropriate `zts_*` structure: -``` -struct zts_sockaddr_in in4; <------ Note the zts_ prefix - ... -zts_bind(fd, (struct sockaddr *)&in4, sizeof(struct zts_sockaddr_in)) < 0) -``` - -If you are calling a host OS function, use your host OS's constants (and structures!): -``` -inet_ntop(AF_INET6, &(in6->sin6_addr), ...); (CORRECT) -inet_ntop(ZTS_AF_INET6, &(in6->sin6_addr), ...); (INCORRECT) -zts_inet_ntop(ZTS_AF_INET6, &(in6->sin6_addr), ...); (CORRECT) -``` - -If you are calling a host OS function but passing a `zts_*` structure, this can work sometimes but you should take care to pass the correct host OS constants: -``` -struct zts_sockaddr_in6 in6; - ... -inet_ntop(AF_INET6, &(in6->sin6_addr), dstStr, INET6_ADDRSTRLEN); -``` - -
- -## Thread model (advanced) - -Both the **socket** and **control** interfaces are thread-safe but are implemented differently. The socket interface is implemented using a relatively performant core locking mechanism in lwIP. This can be disabled if you know what you're doing. The control interface is implemented by a single coarse-grained lock. This lock is not a performance bottleneck since it only applies to functions that manipulate the ZeroTier service and are called seldomly. Callback events are generated by a separate thread and are independent from the rest of the API's internal locking mechanism. Not returning from a callback event won't impact the rest of the API but it will prevent your application from receiving future events so it is in your application's best interest to perform as little work as possible in the callback function and promptly return control back to ZeroTier. - -*Note: Internally, `libzt` will spawn a number of threads for various purposes: a thread for the core service, a thread for the network stack, a low priority thread to process callback events, and a thread for each network joined. The vast majority of work is performed by the core service and stack threads.* - -
- -## Debugging - -If you're experiencing odd behavior or something that looks like a bug I would suggest first reading and understanding the following sections: - -* [Common pitfalls](#common-pitfalls) -* [API compatibility with host OS](#api-compatibility-with-host-os) -* [Thread model](#thread-model) - -If the information in those sections hasn't helped, there are a couple of ways to get debug traces out of various parts of the library. - -1) Build the library in debug mode with `make host_debug`. This will prevent the stripping of debug symbols from the library and will enable basic output traces from libzt. - -2) If you believe your problem is in the network stack you can manually enable debug traces for individual modules in `src/lwipopts.h`. Toggle the `*_DEBUG` types from `LWIP_DBG_OFF` to `LWIP_DBG_ON`. And then rebuild. This will come with a significant performance cost. - -3) Enabling network stack statistics. This is useful if you want to monitor the stack's receipt and handling of traffic as well as internal things like memory allocations and cache hits. Protocol and service statistics are available in debug builds of `libzt`. These statistics are detailed fully in the section of `include/ZeroTierSockets.h` that is guarded by `LWIP_STATS`. - - ``` - struct zts_stats_proto stats; - if (zts_get_protocol_stats(ZTS_STATS_PROTOCOL_ICMP, &stats) == ZTS_ERR_OK) { - printf("icmp.recv=%d\n", stats.recv); // Count of received pings - } - if (zts_get_protocol_stats(ZTS_STATS_PROTOCOL_TCP, &stats) == ZTS_ERR_OK) { - printf("tcp.drop=%d\n", stats.drop); // Count of dropped TCP packets - } - ``` - -4) There are a series of additional events which can signal whether the network stack or its virtual network interfaces have been set up properly. See `ZTS_EVENT_STACK_*` and `ZTS_EVENT_NETIF_*`. - -
- -## Licensing - -ZeroTier is licensed under the BSL version 1.1. See [LICENSE.txt](./LICENSE.txt) and the ZeroTier pricing page for details. ZeroTier is free to use internally in businesses and academic institutions and for non-commercial purposes. Certain types of commercial use such as building closed-source apps and devices based on ZeroTier or offering ZeroTier network controllers and network management as a SaaS service require a commercial license. - -A small amount of third party code is also included in ZeroTier and is not subject to our BSL license. See [AUTHORS.md](ext/ZeroTierOne/AUTHORS.md) for a list of third party code, where it is included, and the licenses that apply to it. All of the third party code in ZeroTier is liberally licensed (MIT, BSD, Apache, public domain, etc.). If you want a commercial license to use the ZeroTier SDK in your product contact us directly via [contact@zerotier.com](mailto:contact@zerotier.com) +# Licensing +ZeroTier and the ZeroTier SDK (libzt and libztcore) are licensed under the [BSL version 1.1](./LICENSE.txt). ZeroTier is free to use internally in businesses and academic institutions and for non-commercial purposes. Certain types of commercial use such as building closed-source apps and devices based on ZeroTier or offering ZeroTier network controllers and network management as a SaaS service require a commercial license. A small amount of third party code is also included in ZeroTier and is not subject to our BSL license. See [AUTHORS.md](ext/ZeroTierOne/AUTHORS.md) for a list of third party code, where it is included, and the licenses that apply to it. All of the third party code in ZeroTier is liberally licensed (MIT, BSD, Apache, public domain, etc.). If you want a commercial license to use the ZeroTier SDK in your product contact us directly via [contact@zerotier.com](mailto:contact@zerotier.com) diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..bf59dcc --- /dev/null +++ b/build.ps1 @@ -0,0 +1,206 @@ +function Build-Host +{ + $Arch="x64" + $Artifact="host" + $BuildType="Debug" + $Variant="-DBUILD_HOST=1" + + # Directory for CMake to build and store intermediate files + $env:BuildDir="cache\win-$Arch-$Artifact-"+$BuildType.ToLower() + md $env:BuildDir -ErrorAction:'silentlycontinue' + # Directory where we plan to store the resultant libraries + $env:OutputDir="dist\win-$Arch-$Artifact-"+$BuildType.ToLower() + md $env:OutputDir -ErrorAction:'silentlycontinue' + pushd -Path $env:BuildDir + cmake $Variant -G "Visual Studio 16 2019" -A $Arch ../../ + cmake --build . --config $BuildType + ctest -C debug + popd + # + md $env:OutputDir\lib\ -ErrorAction:'silentlycontinue' + md $env:OutputDir\bin\ -ErrorAction:'silentlycontinue' + cp $env:BuildDir\lib\$BuildType\zt.lib $env:OutputDir\lib\libzt.lib + cp $env:BuildDir\bin\$BuildType\*.exe $env:OutputDir\bin + cp $env:BuildDir\lib\$BuildType\zt-shared.dll $env:OutputDir\lib\libzt.dll + cp $env:BuildDir\lib\$BuildType\zt-shared.pdb $env:OutputDir\lib\libzt.pdb -ErrorAction:'silentlycontinue' + tree /F $env:OutputDir +} + +function Build-Library([string]$BuildType, [string]$Arch, [string]$LangBinding) +{ + $OptLangBinding="" + + if ($LangBinding -eq "csharp") { + $OptLangBinding="-DZTS_ENABLE_PINVOKE=1" + $LangBindingPostfix="pinvoke" + } + if ($LangBinding -eq "java") { + $OptLangBinding="-DZTS_ENABLE_JAVA=1" + $LangBindingPostfix="jni" + } + + $archAlias = "" + $bitCount = "" + + if ($Arch -eq "Win32") { + $bitCount="32" + $archAlias="win-x86" + } + if ($Arch -eq "x64") { + $bitCount="64" + $archAlias="win-x64" + } + #if ($Arch -eq "ARM32") { + # $bitCount="32" + # $archAlias="win-arm" + #} + if ($Arch -eq "ARM") { + $bitCount="64" + $archAlias="win-arm64" + } + + if ($archAlias -eq "" -or $bitCount -eq "") { + echo "No valid architecture specified. Breaking." + break + } + + # Directory for CMake to build and store intermediate files + $env:BuildDir="cache\win-$Arch-$LangBindingPostfix-"+$BuildType.ToLower() + md $env:BuildDir -ErrorAction:'silentlycontinue' + # Directory where we plan to store the resultant libraries + $env:OutputDir="dist\win-$Arch-$LangBindingPostfix-"+$BuildType.ToLower() + md $env:OutputDir -ErrorAction:'silentlycontinue' + pushd -Path $env:BuildDir + cmake ${OptLangBinding} -G "Visual Studio 16 2019" -A $Arch ../../ + cmake --build . --config $BuildType + popd + md $env:OutputDir\lib\ -ErrorAction:'silentlycontinue' + #cp $env:BuildDir\$BuildType\zt.lib $env:OutputDir\lib\libzt.lib + cp $env:BuildDir\$BuildType\zt-shared.dll $env:OutputDir\lib\libzt.dll + cp $env:BuildDir\$BuildType\zt-shared.pdb $env:OutputDir\lib\libzt.pdb -ErrorAction:'silentlycontinue' +} + +function Build-All +{ + # Win32 + Build-Library -BuildType "Release" -Arch "Win32" -LangBinding "" + Build-Library -BuildType "Release" -Arch "Win32" -LangBinding "csharp" + Build-Library -BuildType "Debug" -Arch "Win32" -LangBinding "" + Build-Library -BuildType "Debug" -Arch "Win32" -LangBinding "csharp" + # x64 + Build-Library -BuildType "Release" -Arch "x64" -LangBinding "" + Build-Library -BuildType "Release" -Arch "x64" -LangBinding "csharp" + Build-Library -BuildType "Debug" -Arch "x64" -LangBinding "" + Build-Library -BuildType "Debug" -Arch "x64" -LangBinding "csharp" +} + +function BuildNuGetPackages([string]$Version) +{ + BuildNuGetPackage-Sockets -BuildType "Release" -Arch "x64" -Version $Version + BuildNuGetPackage-Sockets -BuildType "Debug" -Arch "x64" -Version $Version + BuildNuGetPackage-Sockets -BuildType "Release" -Arch "Win32" -Version $Version + BuildNuGetPackage-Sockets -BuildType "Debug" -Arch "Win32" -Version $Version +} + +function BuildNuGetPackage-Sockets([string]$BuildType, [string]$Arch, [string]$Version) +{ + $archAlias = $Arch + if ($Arch -eq "Win32") { + $archAlias="x86" + } + + $TargetTuple = "win-"+$archAlias+"-nuget-"+$($BuildType.ToLower()) + + # Where we plan to output *.nupkg(s) + md pkg\nuget\ZeroTier.Sockets\bin\ -Force + md dist\$TargetTuple -Force + del dist\$TargetTuple\*.nupkg -ErrorAction:'silentlycontinue' + + # licenses + md pkg\nuget\ZeroTier.Sockets\licenses -Force + cp LICENSE.txt pkg\nuget\ZeroTier.Sockets\licenses + + # contentFiles (sources) + md pkg\nuget\ZeroTier.Sockets\contentFiles -Force + cp src\bindings\csharp\*.cs pkg\nuget\ZeroTier.Sockets\contentFiles + cp examples\csharp\*.cs pkg\nuget\ZeroTier.Sockets\contentFiles + + # runtimes + md pkg\nuget\ZeroTier.Sockets\runtimes\win10-$archAlias\native -Force + md pkg\nuget\ZeroTier.Sockets\runtimes\win10-$archAlias\lib\uap10.0 -Force + #md pkg\nuget\ZeroTier.Sockets\runtimes\win10-arm\native -Force + + # Build wrapper library for C# ZeroTier.Sockets abstraction + csc -target:library -debug:pdbonly ` + -pdb:pkg\nuget\ZeroTier.Sockets\bin\ZeroTier.Sockets.pdb ` + -out:pkg\nuget\ZeroTier.Sockets\bin\ZeroTier.Sockets.dll ` + .\src\bindings\csharp\*.cs + + # Build unmanaged native libzt.dll with exported P/INVOKE symbols + Build-Library -BuildType $BuildType -Arch $Arch -LangBinding "csharp" + + # Copy native libzt.dll into package tree + cp .\dist\win-$archAlias-pinvoke-$($BuildType.ToLower())\lib\*.dll ` + pkg\nuget\ZeroTier.Sockets\bin\libzt.dll + + # .NET Framework + md pkg\nuget\ZeroTier.Sockets\lib\net40 -Force + md pkg\nuget\ZeroTier.Sockets\lib\net403 -Force + md pkg\nuget\ZeroTier.Sockets\lib\net45 -Force + md pkg\nuget\ZeroTier.Sockets\lib\net451 -Force + md pkg\nuget\ZeroTier.Sockets\lib\net452 -Force + md pkg\nuget\ZeroTier.Sockets\lib\net46 -Force + md pkg\nuget\ZeroTier.Sockets\lib\net461 -Force + md pkg\nuget\ZeroTier.Sockets\lib\net462 -Force + md pkg\nuget\ZeroTier.Sockets\lib\net47 -Force + md pkg\nuget\ZeroTier.Sockets\lib\net471 -Force + md pkg\nuget\ZeroTier.Sockets\lib\net472 -Force + md pkg\nuget\ZeroTier.Sockets\lib\net48 -Force + + # .NET "Core" 5.0 (moniker missing from microsoft documentation?) + md pkg\nuget\ZeroTier.Sockets\lib\net5.0 -Force + + # Copy assemblies into framework-specific directories. + $folders = Get-ChildItem pkg\nuget\ZeroTier.Sockets\lib\ + foreach ($folder in $folders.name){ + cp -Path "pkg\nuget\ZeroTier.Sockets\bin\*.*" ` + -Destination "pkg\nuget\ZeroTier.Sockets\lib\$folder" -Recurse + } + + # Native DLL placement + + cp .\dist\win-$archAlias-pinvoke-$($BuildType.ToLower())\lib\*.dll ` + pkg\nuget\ZeroTier.Sockets\runtimes\win10-$archAlias\lib\uap10.0\libzt.dll + cp .\dist\win-$archAlias-pinvoke-$($BuildType.ToLower())\lib\*.dll ` + pkg\nuget\ZeroTier.Sockets\lib\net40\libzt.dll + cp .\dist\win-$archAlias-pinvoke-$($BuildType.ToLower())\lib\*.dll ` + pkg\nuget\ZeroTier.Sockets\runtimes\win10-$archAlias\native\libzt.dll + cp .\dist\win-$archAlias-pinvoke-$($BuildType.ToLower())\lib\*.pdb ` + pkg\nuget\ZeroTier.Sockets\runtimes\win10-$archAlias\lib\uap10.0\libzt.pdb + + # Package + pushd -Path pkg\nuget\ZeroTier.Sockets + nuget pack ZeroTier.Sockets.$archAlias.nuspec ` + -Version $Version -OutputDirectory ..\..\..\dist\$TargetTuple\ + popd +} + +function Clean-PackageDirectory +{ + rm pkg\nuget\ZeroTier.Sockets\lib ` + -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' + rm pkg\nuget\ZeroTier.Sockets\contentFiles ` + -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' + rm pkg\nuget\ZeroTier.Sockets\licenses ` + -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' + rm pkg\nuget\ZeroTier.Sockets\runtimes ` + -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' + rm pkg\nuget\ZeroTier.Sockets\bin ` + -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' +} + +function Clean +{ + rm cache -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' + rm dist -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' +} diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..5c3ab3b --- /dev/null +++ b/build.sh @@ -0,0 +1,543 @@ +#!/bin/bash + +# ----------------------------------------------------------------------------- +# | SYSTEM DISCOVERY AND CONFIGURATION | +# ----------------------------------------------------------------------------- + +# Find and set cmake +CMAKE=cmake3 +if [[ $(which $CMAKE) = "" ]]; +then + CMAKE=cmake # try this next +fi +if [[ $(which $CMAKE) = "" ]]; +then + echo "CMake (cmake) not found. Please install before continuing." + exit +fi + +# +if [[ ! $(which tree) = "" ]]; +then + TREE=tree +else + TREE="du -a " +fi + +# Determine operating system +OSNAME=$(uname | tr '[A-Z]' '[a-z]') +if [[ $OSNAME = *"darwin"* ]]; then + SHARED_LIB_NAME="libzt.dylib" + STATIC_LIB_NAME="libzt.a" + HOST_PLATFORM="macos" +fi +if [[ $OSNAME = *"linux"* ]]; then + SHARED_LIB_NAME="libzt.so" + STATIC_LIB_NAME="libzt.a" + HOST_PLATFORM="linux" +fi + +# Determine and normalize machine type +HOST_MACHINE_TYPE=$(uname -m) +if [[ $HOST_MACHINE_TYPE = *"x86_64"* ]]; then + HOST_MACHINE_TYPE="x64" +fi + +# Determine number of cores. We'll tell CMake to use them all +if [[ $OSNAME = *"darwin"* ]]; then + N_PROCESSORS=$(sysctl -n hw.ncpu) +fi +if [[ $OSNAME = *"linux"* ]]; then + N_PROCESSORS=$(nproc --all) +fi + +# How many processor cores CMake should use during builds, +# comment out the below line out if you don't want parallelism: +BUILD_CONCURRENCY="-j $N_PROCESSORS" + +# ----------------------------------------------------------------------------- +# | PATHS | +# ----------------------------------------------------------------------------- + +# Where we place all finished artifacts +BUILD_OUTPUT_DIR=$(pwd)/dist +# Where we tell CMake to place its build systems and their caches +BUILD_CACHE_DIR=$(pwd)/cache +# Where package projects, scripts, spec files, etc live +PKG_DIR=$(pwd)/pkg +# Default location for (host) libraries +DEFAULT_HOST_LIB_OUTPUT_DIR=$BUILD_OUTPUT_DIR/$HOST_PLATFORM-$HOST_MACHINE_TYPE +# Default location for (host) binaries +DEFAULT_HOST_BIN_OUTPUT_DIR=$BUILD_OUTPUT_DIR/$HOST_PLATFORM-$HOST_MACHINE_TYPE +# Default location for (host) packages +DEFAULT_HOST_PKG_OUTPUT_DIR=$BUILD_OUTPUT_DIR/$HOST_PLATFORM-$HOST_MACHINE_TYPE +# Defaultlocation for CMake's caches (when building for host) +DEFAULT_HOST_BUILD_CACHE_DIR=$BUILD_CACHE_DIR/$HOST_PLATFORM-$HOST_MACHINE_TYPE + +gethosttype() +{ + echo $HOST_PLATFORM-$HOST_MACHINE_TYPE +} + +# ----------------------------------------------------------------------------- +# | TARGETS | +# ----------------------------------------------------------------------------- + +# Build xcframework +# +# ./build.sh xcframework "debug" +# +# Example output: +# +# - Cache : /Volumes/$USER/zt/libzt/libzt-dev/cache/apple-xcframework-debug +# - Build output : /Volumes/$USER/zt/libzt/libzt-dev/dist +# +# apple-xcframework-debug +# └── pkg +# └── zt.xcframework +# ├── Info.plist +# ├── ios-arm64 +# │   └── zt.framework +# │   └── ... +# ├── ios-arm64_x86_64-simulator +# │   └── zt.framework +# │   └── ... +# └── macos-arm64_x86_64 +# └── zt.framework +# └── ... +# +xcframework() +{ + if [[ ! $OSNAME = *"darwin"* ]]; then + echo "Can only build this on a Mac" + exit 0 + fi + BUILD_TYPE=${1:-release} + UPPERCASE_BUILD_TYPE="$(tr '[:lower:]' '[:upper:]' <<< ${BUILD_TYPE:0:1})${BUILD_TYPE:1}" + + # Build all frameworks + macos-framework $BUILD_TYPE + iphoneos-framework $BUILD_TYPE + iphonesimulator-framework $BUILD_TYPE + + ARTIFACT="xcframework" + TARGET_PLATFORM="apple" + TARGET_BUILD_DIR=$BUILD_OUTPUT_DIR/$TARGET_PLATFORM-$ARTIFACT-$BUILD_TYPE + rm -rf $TARGET_BUILD_DIR + PKG_OUTPUT_DIR=$TARGET_BUILD_DIR/pkg + mkdir -p $PKG_OUTPUT_DIR + + MACOS_FRAMEWORK_DIR=macos-x64-framework-$BUILD_TYPE + IOS_FRAMEWORK_DIR=iphoneos-arm64-framework-$BUILD_TYPE + IOS_SIM_FRAMEWORK_DIR=iphonesimulator-x64-framework-$BUILD_TYPE + + # Pack everything + rm -rf $PKG_OUTPUT_DIR/zt.xcframework # Remove prior to move to prevent error + xcodebuild -create-xcframework \ + -framework $BUILD_CACHE_DIR/$MACOS_FRAMEWORK_DIR/lib/$UPPERCASE_BUILD_TYPE/zt.framework \ + -framework $BUILD_CACHE_DIR/$IOS_FRAMEWORK_DIR/lib/$UPPERCASE_BUILD_TYPE/zt.framework \ + -framework $BUILD_CACHE_DIR/$IOS_SIM_FRAMEWORK_DIR/lib/$UPPERCASE_BUILD_TYPE/zt.framework \ + -output $PKG_OUTPUT_DIR/zt.xcframework +} + +# Build iOS framework +# +# ./build.sh iphonesimulator-framework "debug" +# +# Example output: +# +# - Cache : /Volumes/$USER/zt/libzt/libzt-dev/cache/iphonesimulator-x64-framework-debug +# - Build output : /Volumes/$USER/zt/libzt/libzt-dev/dist +# +# /Volumes/$USER/zt/libzt/libzt-dev/dist/iphonesimulator-x64-framework-debug +# └── pkg +# └── zt.framework +# ├── Headers +# │   └── ZeroTierSockets.h +# ├── Info.plist +# ├── Modules +# │   └── module.modulemap +# └── zt +# +iphonesimulator-framework() +{ + if [[ ! $OSNAME = *"darwin"* ]]; then + echo "Can only build this on a Mac" + exit 0 + fi + ARTIFACT="framework" + BUILD_TYPE=${1:-Release} + UPPERCASE_BUILD_TYPE="$(tr '[:lower:]' '[:upper:]' <<< ${BUILD_TYPE:0:1})${BUILD_TYPE:1}" + VARIANT="-DBUILD_IOS_FRAMEWORK=True" + TARGET_PLATFORM="iphonesimulator" + TARGET_MACHINE_TYPE="x64" # presumably + CACHE_DIR=$BUILD_CACHE_DIR/$TARGET_PLATFORM-$TARGET_MACHINE_TYPE-$ARTIFACT-$BUILD_TYPE + TARGET_BUILD_DIR=$BUILD_OUTPUT_DIR/$TARGET_PLATFORM-$TARGET_MACHINE_TYPE-$ARTIFACT-$BUILD_TYPE + rm -rf $TARGET_BUILD_DIR + PKG_OUTPUT_DIR=$TARGET_BUILD_DIR/pkg + mkdir -p $PKG_OUTPUT_DIR + # Generate project + mkdir -p $CACHE_DIR + cd $CACHE_DIR + # iOS (SDK 11+, 64-bit only, arm64) + $CMAKE -G Xcode ../../ $VARIANT + # Build framework + xcodebuild -target zt -configuration "$UPPERCASE_BUILD_TYPE" -sdk "iphonesimulator" + cd - + cp -rf $CACHE_DIR/lib/$UPPERCASE_BUILD_TYPE/*.framework $PKG_OUTPUT_DIR + echo -e "\n - Build cache : $CACHE_DIR\n - Build output : $BUILD_OUTPUT_DIR\n" + $TREE $TARGET_BUILD_DIR +} + +# Build macOS framework +# +# ./build.sh macos-framework "debug" +# +# Example output: +# +# - Cache : /Volumes/$USER/zt/libzt/libzt-dev/cache/macos-x64-framework-debug +# - Build output : /Volumes/$USER/zt/libzt/libzt-dev/dist +# +# /Volumes/$USER/zt/libzt/libzt-dev/dist/macos-x64-framework-debug +# └── pkg +# └── zt.framework +# ├── Headers +# │   └── ZeroTierSockets.h +# ├── Info.plist +# ├── Modules +# │   └── module.modulemap +# └── zt +# +macos-framework() +{ + if [[ ! $OSNAME = *"darwin"* ]]; then + echo "Can only build this on a Mac" + exit 0 + fi + ARTIFACT="framework" + BUILD_TYPE=${1:-Release} + UPPERCASE_BUILD_TYPE="$(tr '[:lower:]' '[:upper:]' <<< ${BUILD_TYPE:0:1})${BUILD_TYPE:1}" + VARIANT="-DBUILD_MACOS_FRAMEWORK=True" + CACHE_DIR=$DEFAULT_HOST_BUILD_CACHE_DIR-$ARTIFACT-$BUILD_TYPE + TARGET_BUILD_DIR=$DEFAULT_HOST_BIN_OUTPUT_DIR-$ARTIFACT-$BUILD_TYPE + rm -rf $TARGET_BUILD_DIR + PKG_OUTPUT_DIR=$TARGET_BUILD_DIR/pkg + mkdir -p $PKG_OUTPUT_DIR + # Generate project + mkdir -p $CACHE_DIR + cd $CACHE_DIR + $CMAKE -G Xcode ../../ $VARIANT + # Build framework + xcodebuild -target zt -configuration $UPPERCASE_BUILD_TYPE -sdk "macosx" + cd - + cp -rf $CACHE_DIR/lib/$UPPERCASE_BUILD_TYPE/*.framework $PKG_OUTPUT_DIR + echo -e "\n - Build cache : $CACHE_DIR\n - Build output : $BUILD_OUTPUT_DIR\n" + $TREE $TARGET_BUILD_DIR +} + +# Build iOS framework +# +# ./build.sh iphoneos-framework "debug" +# +# Example output: +# +# - Cache : /Volumes/$USER/zt/libzt/libzt-dev/cache/iphoneos-arm64-framework-debug +# - Build output : /Volumes/$USER/zt/libzt/libzt-dev/dist +# +# /Volumes/$USER/zt/libzt/libzt-dev/dist/iphoneos-arm64-framework-debug +# └── pkg +# └── zt.framework +# ├── Headers +# │   └── ZeroTierSockets.h +# ├── Info.plist +# ├── Modules +# │   └── module.modulemap +# └── zt +# +iphoneos-framework() +{ + if [[ ! $OSNAME = *"darwin"* ]]; then + echo "Can only build this on a Mac" + exit 0 + fi + ARTIFACT="framework" + BUILD_TYPE=${1:-Release} + UPPERCASE_BUILD_TYPE="$(tr '[:lower:]' '[:upper:]' <<< ${BUILD_TYPE:0:1})${BUILD_TYPE:1}" + VARIANT="-DBUILD_IOS_FRAMEWORK=True -DIOS_ARM64=True" + TARGET_PLATFORM="iphoneos" + TARGET_MACHINE_TYPE=arm64 + CACHE_DIR=$BUILD_CACHE_DIR/$TARGET_PLATFORM-$TARGET_MACHINE_TYPE-$ARTIFACT-$BUILD_TYPE + TARGET_BUILD_DIR=$BUILD_OUTPUT_DIR/$TARGET_PLATFORM-$TARGET_MACHINE_TYPE-$ARTIFACT-$BUILD_TYPE + rm -rf $TARGET_BUILD_DIR + PKG_OUTPUT_DIR=$TARGET_BUILD_DIR/pkg + mkdir -p $PKG_OUTPUT_DIR + # Generate project + mkdir -p $CACHE_DIR + cd $CACHE_DIR + # iOS (SDK 11+, 64-bit only, arm64) + $CMAKE -G Xcode ../../ $VARIANT + sed -i '' 's/x86_64/$(CURRENT_ARCH)/g' zt.xcodeproj/project.pbxproj + # Build framework + xcodebuild -arch $TARGET_MACHINE_TYPE -target zt -configuration "$UPPERCASE_BUILD_TYPE" -sdk "iphoneos" + cd - + cp -rvf $CACHE_DIR/lib/$UPPERCASE_BUILD_TYPE/*.framework $PKG_OUTPUT_DIR + echo -e "\n - Build cache : $CACHE_DIR\n - Build output : $BUILD_OUTPUT_DIR\n" + $TREE $TARGET_BUILD_DIR +} + +# Build standard libraries, examples, and selftest +# +# ./build.sh host "release" +# +# Example output: +# +# - Cache : /Volumes/$USER/zt/libzt/libzt-dev/cache/linux-x64-host-release +# - Build output : /Volumes/$USER/zt/libzt/libzt-dev/dist +# +# linux-x64-host-release +# ├── bin +# │   ├── client +# │   └── server +# └── lib +#  ├── libzt.a +#  └── libzt.so # .dylib, .dll +# +host() +{ + ARTIFACT="host" + # Default to release + BUILD_TYPE=${1:-release} + VARIANT="-DBUILD_HOST=True" + CACHE_DIR=$DEFAULT_HOST_BUILD_CACHE_DIR-$ARTIFACT-$BUILD_TYPE + TARGET_BUILD_DIR=$DEFAULT_HOST_BIN_OUTPUT_DIR-$ARTIFACT-$BUILD_TYPE + rm -rf $TARGET_BUILD_DIR + LIB_OUTPUT_DIR=$TARGET_BUILD_DIR/lib + BIN_OUTPUT_DIR=$TARGET_BUILD_DIR/bin + mkdir -p $LIB_OUTPUT_DIR + mkdir -p $BIN_OUTPUT_DIR + $CMAKE $VARIANT -H. -B$CACHE_DIR -DCMAKE_BUILD_TYPE=$BUILD_TYPE + $CMAKE --build $CACHE_DIR $BUILD_CONCURRENCY + cp -f $CACHE_DIR/lib/libzt.* $LIB_OUTPUT_DIR + cp -f $CACHE_DIR/bin/* $BIN_OUTPUT_DIR + echo -e "\n - Build cache : $CACHE_DIR\n - Build output : $BUILD_OUTPUT_DIR\n" + $TREE $TARGET_BUILD_DIR +} +host-install() +{ + cd cache/$HOST_PLATFORM-$HOST_MACHINE_TYPE-host-$1/ + make install + cd - +} +host-uninstall() +{ + cd cache/$HOST_PLATFORM-$HOST_MACHINE_TYPE-host-$1/ + xargs rm < install_manifest.txt + cd - +} + +# Build shared library with python wrapper symbols exported +host-python() +{ + ARTIFACT="python" + # Default to release + BUILD_TYPE=${1:-release} + VARIANT="-DZTS_ENABLE_PYTHON=True" + CACHE_DIR=$DEFAULT_HOST_BUILD_CACHE_DIR-$ARTIFACT-$BUILD_TYPE + TARGET_BUILD_DIR=$DEFAULT_HOST_BIN_OUTPUT_DIR-$ARTIFACT-$BUILD_TYPE + rm -rf $TARGET_BUILD_DIR + LIB_OUTPUT_DIR=$TARGET_BUILD_DIR/lib + BIN_OUTPUT_DIR=$TARGET_BUILD_DIR/bin + mkdir -p $LIB_OUTPUT_DIR + # Optional step to generate new SWIG wrapper + swig -c++ -python -o src/bindings/python/zt_wrap.cpp -Iinclude src/bindings/python/zt.i + $CMAKE $VARIANT -H. -B$CACHE_DIR -DCMAKE_BUILD_TYPE=$BUILD_TYPE + $CMAKE --build $CACHE_DIR $BUILD_CONCURRENCY + cp -f $CACHE_DIR/lib/$SHARED_LIB_NAME $LIB_OUTPUT_DIR/_libzt.so + echo -e "\n - Build cache : $CACHE_DIR\n - Build output : $BUILD_OUTPUT_DIR\n" + $TREE $TARGET_BUILD_DIR +} + +# Build shared library with P/INVOKE wrapper symbols exported +host-pinvoke() +{ + ARTIFACT="pinvoke" + # Default to release + BUILD_TYPE=${1:-release} + VARIANT="-DZTS_ENABLE_PINVOKE=True" + CACHE_DIR=$DEFAULT_HOST_BUILD_CACHE_DIR-$ARTIFACT-$BUILD_TYPE + TARGET_BUILD_DIR=$DEFAULT_HOST_BIN_OUTPUT_DIR-$ARTIFACT-$BUILD_TYPE + rm -rf $TARGET_BUILD_DIR + LIB_OUTPUT_DIR=$TARGET_BUILD_DIR/lib + BIN_OUTPUT_DIR=$TARGET_BUILD_DIR/bin + mkdir -p $LIB_OUTPUT_DIR + $CMAKE $VARIANT -H. -B$CACHE_DIR -DCMAKE_BUILD_TYPE=$BUILD_TYPE + $CMAKE --build $CACHE_DIR $BUILD_CONCURRENCY + cp -f $CACHE_DIR/lib/libzt.* $LIB_OUTPUT_DIR + echo -e "\n - Build cache : $CACHE_DIR\n - Build output : $BUILD_OUTPUT_DIR\n" + $TREE $TARGET_BUILD_DIR +} + +# Build shared library with Java JNI wrapper symbols exported (.jar) +host-jar() +{ + ARTIFACT="jar" + # Default to release + BUILD_TYPE=${1:-release} + VARIANT="-DZTS_ENABLE_JAVA=True" + CACHE_DIR=$DEFAULT_HOST_BUILD_CACHE_DIR-$ARTIFACT-$BUILD_TYPE + TARGET_BUILD_DIR=$DEFAULT_HOST_BIN_OUTPUT_DIR-$ARTIFACT-$BUILD_TYPE + rm -rf $TARGET_BUILD_DIR + PKG_OUTPUT_DIR=$TARGET_BUILD_DIR/pkg + mkdir -p $PKG_OUTPUT_DIR + # Share same cache dir with CMake + JAVA_JAR_DIR=$CACHE_DIR/pkg/jar + JAVA_JAR_SOURCE_TREE_DIR=$JAVA_JAR_DIR/com/zerotier/libzt/ + mkdir -p $JAVA_JAR_SOURCE_TREE_DIR + cp -f src/bindings/java/*.java $JAVA_JAR_SOURCE_TREE_DIR + # Build + $CMAKE $VARIANT -H. -B$CACHE_DIR -DCMAKE_BUILD_TYPE=$BUILD_TYPE + $CMAKE --build $CACHE_DIR $BUILD_CONCURRENCY + # Package everything + cp -f $CACHE_DIR/lib/libzt.* $JAVA_JAR_DIR + cd $JAVA_JAR_DIR + export JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8 + javac com/zerotier/libzt/*.java + jar cf libzt-"$(git describe --abbrev=0)".jar $SHARED_LIB_NAME com/zerotier/libzt/*.class + rm -rf com $SHARED_LIB_NAME + cd - + # Copy JAR to dist/ + echo -e "\nContents of JAR:\n" + jar tf $JAVA_JAR_DIR/*.jar + echo -e + mv $JAVA_JAR_DIR/*.jar $PKG_OUTPUT_DIR + echo -e "\n - Build cache : $CACHE_DIR\n - Build output : $BUILD_OUTPUT_DIR\n" + $TREE $TARGET_BUILD_DIR +} + + +# ----------------------------------------------------------------------------- +# | ANDROID CONFIG | +# ----------------------------------------------------------------------------- + +ANDROID_PKG_PROJ_DIR=$(pwd)/pkg/android + +# Set ANDROID_HOME because setting sdk.dir in local.properties isn't always reliable +#export PATH=/Library/Java/JavaVirtualMachines/$JDK/Contents/Home/bin/:${PATH} +#export PATH=/Users/$USER/Library/Android/sdk/platform-tools/:${PATH} +GRADLE_ARGS=--stacktrace +#ANDROID_APP_NAME=com.example.mynewestapplication +# for our purposes we limit this to execution on macOS +if [[ $OSNAME = *"linux"* ]]; then + export ANDROID_HOME=/usr/lib/android-sdk/ +fi +if [[ $OSNAME = *"darwin"* ]]; then + export ANDROID_HOME=/Users/$USER/Library/Android/sdk +fi + +# Build shared library with Java JNI wrapper symbols exported (.aar) +# +# ./build.sh android-aar "release" +# +# Example output: +# +# - Cache : /Volumes/$USER/zt/libzt/libzt-dev/cache/android-any-android-release +# - Build output : /Volumes/$USER/zt/libzt/libzt-dev/dist +# +# android-any-android-release +# └── libzt-release.aar +# +android-aar() +{ + ARTIFACT="android" + BUILD_TYPE=${1:-release} # Default to release + CMAKE_SWITCH="ZTS_ENABLE_JAVA" + TARGET_PLATFORM="android" + TARGET_MACHINE_TYPE=any + CACHE_DIR=$BUILD_CACHE_DIR/$TARGET_PLATFORM-$TARGET_MACHINE_TYPE-$ARTIFACT-$BUILD_TYPE + PKG_OUTPUT_DIR=$BUILD_OUTPUT_DIR/$TARGET_PLATFORM-$TARGET_MACHINE_TYPE-$ARTIFACT-$BUILD_TYPE + mkdir -p $CACHE_DIR + mkdir -p $PKG_OUTPUT_DIR + # Unsure why, but Gradle's build script chokes on this non-source file now + rm -rf ext/ZeroTierOne/ext/miniupnpc/VERSION + export PATH=$ANDROID_HOME/cmdline-tools/tools/bin:$PATH + # Copy source files into project + cp -f src/bindings/java/*.java ${ANDROID_PKG_PROJ_DIR}/app/src/main/java/com/zerotier/libzt + # Build + UPPERCASE_BUILD_TYPE="$(tr '[:lower:]' '[:upper:]' <<< ${BUILD_TYPE:0:1})${BUILD_TYPE:1}" + CMAKE_FLAGS="-D${CMAKE_SWITCH}=1 -D${CMAKE_SWITCH}=ON" + cd $ANDROID_PKG_PROJ_DIR + ./gradlew $GRADLE_ARGS assemble$UPPERCASE_BUILD_TYPE # assembleRelease / assembleDebug + mv $ANDROID_PKG_PROJ_DIR/app/build/outputs/aar/*.aar \ + $PKG_OUTPUT_DIR/libzt-$BUILD_TYPE.aar + cd - + echo -e "\n - Build cache : $CACHE_DIR\n - Build output : $BUILD_OUTPUT_DIR\n" + $TREE $PKG_OUTPUT_DIR +} + +# Build static library and selftest. Currently this only tests +# the core C API, not any of the language bindings. +test() +{ + ARTIFACT="test" + # Default to release + BUILD_TYPE=${1:-release} + VARIANT="-DBUILD_HOST_SELFTEST_ONLY=True" + CACHE_DIR=$DEFAULT_HOST_BUILD_CACHE_DIR-$ARTIFACT-$BUILD_TYPE + TARGET_BUILD_DIR=$DEFAULT_HOST_BIN_OUTPUT_DIR-$ARTIFACT-$BUILD_TYPE + rm -rf $TARGET_BUILD_DIR + LIB_OUTPUT_DIR=$TARGET_BUILD_DIR/lib + BIN_OUTPUT_DIR=$TARGET_BUILD_DIR/bin + mkdir -p $BIN_OUTPUT_DIR + $CMAKE $VARIANT -H. -B$CACHE_DIR -DCMAKE_BUILD_TYPE=$BUILD_TYPE + $CMAKE --build $CACHE_DIR $BUILD_CONCURRENCY + cp -f $CACHE_DIR/bin/* $BIN_OUTPUT_DIR + echo -e "\n - Build cache : $CACHE_DIR\n - Build output : $BUILD_OUTPUT_DIR\n" + $TREE $TARGET_BUILD_DIR + # Test + cd $CACHE_DIR + ctest -C release + cd - +} + +# Recursive deep clean +clean() +{ + # Finished artifacts + rm -rf $BUILD_OUTPUT_DIR + # CMake's build system cache + rm -rf $BUILD_CACHE_DIR + # CMake test output + rm -rf Testing + # Android AAR project binaries and sources (copied from src/bindings/java) + rm -rf $ANDROID_PKG_PROJ_DIR/app/build + rm -rf $ANDROID_PKG_PROJ_DIR/app/src/main/java/com/zerotier/libzt/*.java + rm -rf $ANDROID_PKG_PROJ_DIR/app/.externalNativeBuild + # Remove whatever remains + find . \ + \( -name '*.dylib' \ + -o -name '*.dll' \ + -o -name '*.aar' \ + -o -name '*.jar' \ + -o -name '*.so' \ + -o -name '*.a' \ + -o -name '*.o' \ + -o -name '*.exe' \ + -o -name '*.o.d' \ + -o -name '*.out' \ + -o -name '*.log' \ + -o -name '*.dSYM' \ + -o -name '*.class' \ + \) -exec rm -rf {} + + + find . -type d -name "__pycache__" -exec rm -rf {} + +} + +list() +{ + IFS=$'\n' + for f in $(declare -F); do + echo "${f:11}" + done +} + +"$@" diff --git a/dist.ps1 b/dist.ps1 deleted file mode 100644 index ff3c440..0000000 --- a/dist.ps1 +++ /dev/null @@ -1,175 +0,0 @@ -function Clean -{ - Remove-Item builds -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' - Remove-Item tmp -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' - Remove-Item lib -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' - Remove-Item bin -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' - # pkg - Clean-PackageDirectory - Get-ChildItem pkg -recurse -include *.dll | remove-item - Get-ChildItem pkg -recurse -include *.lib | remove-item - Get-ChildItem pkg -recurse -include *.pdb | remove-item - Get-ChildItem pkg -recurse -include *.nupkg | remove-item - # src - Get-ChildItem src -recurse -include *.dll | remove-item - Get-ChildItem src -recurse -include *.lib | remove-item - Get-ChildItem src -recurse -include *.pdb | remove-item - Get-ChildItem src -recurse -include *.dylib | remove-item - Get-ChildItem src -recurse -include *.so | remove-item - Get-ChildItem src -recurse -include *.exe | remove-item - Get-ChildItem src -recurse -include *.out | remove-item - Get-ChildItem src -recurse -include *.a | remove-item -} - -function Build-Library([string]$BuildType, [string]$Arch, [string]$LanguageBinding) -{ - $OptionalLanguageBinding="" - - if ($LanguageBinding -eq "csharp") { - $OptionalLanguageBinding="-DZTS_PINVOKE:BOOL=ON" - $LanguageBindingPostfix="-pinvoke" - } - if ($LanguageBinding -eq "java") { - #$OptionalLanguageBinding="-DSDK_JNI=ON -DSDK_JNI=1" - #$LanguageBindingPostfix="-jni" - } - - $archAlias = "" - $bitCount = "" - - if ($Arch -eq "Win32") { - $bitCount="32" - $archAlias="win-x86" - } - if ($Arch -eq "x64") { - $bitCount="64" - $archAlias="win-x64" - } - #if ($Arch -eq "ARM32") { - # $bitCount="32" - # $archAlias="win-arm" - #} - if ($Arch -eq "ARM") { - $bitCount="64" - $archAlias="win-arm64" - } - - if ($archAlias -eq "" -or $bitCount -eq "") { - echo "No valid architecture specified. Breaking." - break - } - - # Directory for CMake to build and store intermediate files - $env:BuildDir="tmp\$BuildType\"+$Arch+$LanguageBindingPostfix - md $env:BuildDir -ErrorAction:'silentlycontinue' - # Directory where we plan to store the resultant libraries - $env:OutputDir="lib\"+$BuildType.ToLower() - md $env:OutputDir\$archAlias$LanguageBindingPostfix -ErrorAction:'silentlycontinue' - Push-Location -Path $env:BuildDir - cmake ${OptionalLanguageBinding} -G "Visual Studio 16 2019" -A $Arch ../../../ - cmake --build . --config $BuildType - Pop-Location - Copy-Item $env:BuildDir\$BuildType\zt.lib $env:OutputDir\$archAlias$LanguageBindingPostfix\libzt$bitCount.lib - Copy-Item $env:BuildDir\$BuildType\zt-shared.dll $env:OutputDir\$archAlias$LanguageBindingPostfix\libzt$bitCount.dll - Copy-Item $env:BuildDir\$BuildType\zt-shared.pdb $env:OutputDir\$archAlias$LanguageBindingPostfix\libzt$bitCount.pdb -ErrorAction:'silentlycontinue' -} - -function Build-All -{ - # Win32 - Build-Library -BuildType "Release" -Arch "Win32" -LanguageBinding "" - Build-Library -BuildType "Release" -Arch "Win32" -LanguageBinding "pinvoke" - Build-Library -BuildType "Debug" -Arch "Win32" -LanguageBinding "" - Build-Library -BuildType "Debug" -Arch "Win32" -LanguageBinding "pinvoke" - # x64 - Build-Library -BuildType "Release" -Arch "x64" -LanguageBinding "" - Build-Library -BuildType "Release" -Arch "x64" -LanguageBinding "pinvoke" - Build-Library -BuildType "Debug" -Arch "x64" -LanguageBinding "" - Build-Library -BuildType "Debug" -Arch "x64" -LanguageBinding "pinvoke" -} - -function BuildNuGetPackages([string]$Version) -{ - BuildNuGetPackage-Sockets -BuildType "Release" -Arch "x64" -Version $Version - BuildNuGetPackage-Sockets -BuildType "Debug" -Arch "x64" -Version $Version - BuildNuGetPackage-Sockets -BuildType "Release" -Arch "Win32" -Version $Version - BuildNuGetPackage-Sockets -BuildType "Debug" -Arch "Win32" -Version $Version -} - -function BuildNuGetPackage-Sockets([string]$BuildType, [string]$Arch, [string]$Version) -{ - $archAlias = $Arch - if ($Arch -eq "Win32") { - $archAlias="x86" - } - - md pkg\nuget\ZeroTier.Sockets\bin\ -Force - md builds\pkg\nuget\$($BuildType.ToLower())\$archAlias -Force - del builds\pkg\nuget\$($BuildType.ToLower())\$archAlias\*.nupkg -ErrorAction:'silentlycontinue' - - # licenses - md pkg\nuget\ZeroTier.Sockets\licenses -Force - Copy-Item LICENSE.txt pkg\nuget\ZeroTier.Sockets\licenses - - # contentFiles (sources) - md pkg\nuget\ZeroTier.Sockets\contentFiles -Force - Copy-Item src\bindings\csharp\*.cs pkg\nuget\ZeroTier.Sockets\contentFiles - - # Where we plan to output *.nupkg(s) - md builds\pkg\nuget\$($BuildType.ToLower()) -Force - - # runtimes - md pkg\nuget\ZeroTier.Sockets\runtimes\win10-$archAlias\native -Force - md pkg\nuget\ZeroTier.Sockets\runtimes\win10-$archAlias\lib\uap10.0 -Force - #md pkg\nuget\ZeroTier.Sockets\runtimes\win10-arm\native -Force - - # Build wrapper library for C# ZeroTier.Sockets abstraction - csc -target:library -debug:pdbonly -pdb:pkg\nuget\ZeroTier.Sockets\bin\ZeroTier.Sockets.pdb -out:pkg\nuget\ZeroTier.Sockets\bin\ZeroTier.Sockets.dll .\src\bindings\csharp\*.cs - - # Build unmanaged native libzt.dll with exported P/INVOKE symbols - Build-Library -BuildType $BuildType -Arch $Arch -LanguageBinding "csharp" - Copy-Item .\lib\$($BuildType.ToLower())\win-$archAlias-pinvoke\*.dll pkg\nuget\ZeroTier.Sockets\bin\libzt.dll - - # .NET Framework - md pkg\nuget\ZeroTier.Sockets\lib\net40 -Force - md pkg\nuget\ZeroTier.Sockets\lib\net403 -Force - md pkg\nuget\ZeroTier.Sockets\lib\net45 -Force - md pkg\nuget\ZeroTier.Sockets\lib\net451 -Force - md pkg\nuget\ZeroTier.Sockets\lib\net452 -Force - md pkg\nuget\ZeroTier.Sockets\lib\net46 -Force - md pkg\nuget\ZeroTier.Sockets\lib\net461 -Force - md pkg\nuget\ZeroTier.Sockets\lib\net462 -Force - md pkg\nuget\ZeroTier.Sockets\lib\net47 -Force - md pkg\nuget\ZeroTier.Sockets\lib\net471 -Force - md pkg\nuget\ZeroTier.Sockets\lib\net472 -Force - md pkg\nuget\ZeroTier.Sockets\lib\net48 -Force - - # .NET "Core" 5.0 (moniker missing from microsoft documentation?) - md pkg\nuget\ZeroTier.Sockets\lib\net5.0 -Force - - # Copy assemblies into framework-specific directories. - $folders = Get-ChildItem pkg\nuget\ZeroTier.Sockets\lib\ - foreach ($folder in $folders.name){ - Copy-Item -Path "pkg\nuget\ZeroTier.Sockets\bin\*.*" -Destination "pkg\nuget\ZeroTier.Sockets\lib\$folder" -Recurse - } - - # Native DLL placement - Copy-Item .\lib\$($BuildType.ToLower())\win-$archAlias-pinvoke\*.dll pkg\nuget\ZeroTier.Sockets\runtimes\win10-$archAlias\lib\uap10.0\libzt.dll - Copy-Item .\lib\$($BuildType.ToLower())\win-$archAlias-pinvoke\*.dll pkg\nuget\ZeroTier.Sockets\lib\net40\libzt.dll - Copy-Item .\lib\$($BuildType.ToLower())\win-$archAlias-pinvoke\*.dll pkg\nuget\ZeroTier.Sockets\runtimes\win10-$archAlias\native\libzt.dll - Copy-Item .\lib\$($BuildType.ToLower())\win-$archAlias-pinvoke\*.pdb pkg\nuget\ZeroTier.Sockets\runtimes\win10-$archAlias\lib\uap10.0\libzt.pdb - - # Package - Push-Location -Path pkg\nuget\ZeroTier.Sockets - nuget pack ZeroTier.Sockets.$archAlias.nuspec -Version $Version -OutputDirectory ..\..\..\builds\pkg\nuget\$($BuildType.ToLower())\$archAlias - Pop-Location -} - -function Clean-PackageDirectory -{ - Remove-Item pkg\nuget\ZeroTier.Sockets\lib -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' - Remove-Item pkg\nuget\ZeroTier.Sockets\contentFiles -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' - Remove-Item pkg\nuget\ZeroTier.Sockets\licenses -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' - Remove-Item pkg\nuget\ZeroTier.Sockets\runtimes -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' - Remove-Item pkg\nuget\ZeroTier.Sockets\bin -Recurse -Force -Confirm:$false -ErrorAction:'silentlycontinue' -} diff --git a/dist.sh b/dist.sh deleted file mode 100755 index 97b0c40..0000000 --- a/dist.sh +++ /dev/null @@ -1,737 +0,0 @@ -#!/bin/bash - -# This script works in conjunction with the Makefile and CMakeLists.txt. It is -# intended to be called from the Makefile, it generates projects and builds -# targets as specified in CMakeLists.txt. In addition, this script is -# responsible for packaging all of the resultant builds, licenses, and -# documentation as well as controlling the installation and remote execution of -# tests on mobile devices. - -# Example workflow for producing a full release package: -# -# (1) On packaging platform, build most targets (including android and ios): -# (1a) make all -# (1b) make wrap -# (2) On other supported platforms, build remaining supported targets -# and copy them into a directory structure that is expected by a later stage: -# (2a) make all -# (2b) make wrap -# (3) Copy all resultant $(ARCH)_product directories to root project directory -# of packaging platform. For instance: -# -# libzt -# ├── README.md -# ├── products -# ├── linux-x86_64_products -# ├── linux-armv7l_products -# ├── linux-armv6l_products -# ├── products -# ├── win_products -# └── ... -# -# (4) Merge all builds into single `products` directory and package: -# (4a) make clean -# (4a) make dist - -CMAKE=cmake -BUILD_CONCURRENCY= -#"-j 2" -OSNAME=$(uname | tr '[A-Z]' '[a-z]') -BUILD_TMP=$(pwd)/tmp -ANDROID_PROJ_DIR=$(pwd)/pkg/android -XCODE_IOS_PROJ_DIR=$(pwd)/ports/xcode_ios -XCODE_IOS_SIMULATOR_PROJ_DIR=$(pwd)/ports/xcode_ios_simulator -XCODE_MACOS_PROJ_DIR=$(pwd)/ports/xcode_macos - -# Generates wrapper source files for various target languages -generate_swig_wrappers() -{ - SRC=../src - - cd ports/; - - # C# - mkdir -p ${SRC}/csharp - swig -csharp -c++ zt.i - # Prepend our callback garb to libzt.cs, copy new source files into src/csharp - cat csharp/csharp_callback.cs libzt.cs > libzt_concat.cs - rm libzt.cs - mv libzt_concat.cs libzt.cs - mv -f *.cs zt_wrap.cxx ${SRC}/csharp/ - - # Javascript - # Build for all three engines. Why not? - ENGINE=jsc - mkdir -p ${SRC}/js/${ENGINE} - swig -javascript -${ENGINE} -c++ zt.i - mv zt_wrap.cxx ${SRC}/js/${ENGINE} - ENGINE=v8 - mkdir -p ${SRC}/js/${ENGINE} - swig -javascript -${ENGINE} -c++ zt.i - mv zt_wrap.cxx ${SRC}/js/${ENGINE} - ENGINE=node - mkdir -p ${SRC}/js/${ENGINE} - swig -javascript -${ENGINE} -c++ zt.i - mv -f zt_wrap.cxx ${SRC}/js/${ENGINE} - - # Python - mkdir -p ${SRC}/python - swig -python -c++ zt.i - mv -f zt_wrap.cxx *.py ${SRC}/python - - # Lua - mkdir -p ${SRC}/lua - swig -lua -c++ zt.i - mv -f zt_wrap.cxx ${SRC}/lua - - # Go 64 - mkdir -p ${SRC}/go64 - swig -intgosize 64 -go -c++ zt.i - mv -f zt_wrap.cxx *.go *.c ${SRC}/go64 - - # Go 32 - mkdir -p ${SRC}/go32 - swig -intgosize 32 -go -c++ zt.i - mv -f zt_wrap.cxx *.go *.c ${SRC}/go32 - - cd - -} - -# Generates projects if needed -generate_projects() -{ - if [[ ! $OSNAME = *"darwin"* ]]; then - exit 0 - fi - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - if [[ $OSNAME = *"darwin"* ]]; then - # iOS (SDK 11+, 64-bit only, arm64) - if [ ! -d "$XCODE_IOS_PROJ_DIR" ]; then - mkdir -p $XCODE_IOS_PROJ_DIR - cd $XCODE_IOS_PROJ_DIR - $CMAKE -G Xcode ../../ -DIOS_FRAMEWORK=1 -DIOS_ARM64=1 - # Manually replace arch strings in project file - sed -i '' 's/x86_64/$(CURRENT_ARCH)/g' zt.xcodeproj/project.pbxproj - cd - - fi - - if [ ! -d "$XCODE_IOS_SIMULATOR_PROJ_DIR" ]; then - mkdir -p $XCODE_IOS_SIMULATOR_PROJ_DIR - cd $XCODE_IOS_SIMULATOR_PROJ_DIR - $CMAKE -G Xcode ../../ -DIOS_FRAMEWORK=1 - # Manually replace arch strings in project file - #sed -i '' 's/x86_64/$(CURRENT_ARCH)/g' zt.xcodeproj/project.pbxproj - cd - - fi - - # macOS - if [ ! -d "$XCODE_MACOS_PROJ_DIR" ]; then - mkdir -p $XCODE_MACOS_PROJ_DIR - cd $XCODE_MACOS_PROJ_DIR - $CMAKE -G Xcode ../../ -DMACOS_FRAMEWORK=1 - cd - - fi - fi -} - -# Build framework for iOS (with embedded static library) -ios() -{ - if [[ ! $OSNAME = *"darwin"* ]]; then - exit 0 - fi - generate_projects # if needed - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - UPPERCASE_CONFIG="$(tr '[:lower:]' '[:upper:]' <<< ${1:0:1})${1:1}" - - cd $XCODE_IOS_PROJ_DIR - # Framework - xcodebuild -arch arm64 -target zt -configuration "$UPPERCASE_CONFIG" -sdk "iphoneos" - cd - - IOS_OUTPUT_DIR=$(pwd)/lib/$1/ios - mkdir -p $IOS_OUTPUT_DIR - rm -rf $IOS_OUTPUT_DIR/zt.framework # Remove prior to move to prevent error - mv $XCODE_IOS_PROJ_DIR/$UPPERCASE_CONFIG-iphoneos/* $IOS_OUTPUT_DIR - - cd $XCODE_IOS_SIMULATOR_PROJ_DIR - # Framework - xcodebuild -target zt -configuration "$UPPERCASE_CONFIG" -sdk "iphonesimulator" - cd - - SIMULATOR_OUTPUT_DIR=$(pwd)/lib/$1/ios-simulator - mkdir -p $SIMULATOR_OUTPUT_DIR - rm -rf $SIMULATOR_OUTPUT_DIR/zt.framework # Remove prior to move to prevent error - mv $XCODE_IOS_SIMULATOR_PROJ_DIR/$UPPERCASE_CONFIG-iphonesimulator/* $SIMULATOR_OUTPUT_DIR - - # Combine the two archs - lipo -create $IOS_OUTPUT_DIR/zt.framework/zt $SIMULATOR_OUTPUT_DIR/zt.framework/zt -output $IOS_OUTPUT_DIR/zt.framework/zt - - # Clean up - rm -rf $SIMULATOR_OUTPUT_DIR -} - -# Build framework for current host (macOS only) -macos() -{ - if [[ ! $OSNAME = *"darwin"* ]]; then - exit 0 - fi - generate_projects # if needed - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - UPPERCASE_CONFIG="$(tr '[:lower:]' '[:upper:]' <<< ${1:0:1})${1:1}" - cd $XCODE_MACOS_PROJ_DIR - # Framework - xcodebuild -target zt -configuration "$UPPERCASE_CONFIG" -sdk "macosx" - cd - - OUTPUT_DIR=$(pwd)/lib/$1/macos-universal - mkdir -p $OUTPUT_DIR - rm -rf $OUTPUT_DIR/zt.framework # Remove prior to move to prevent error - mv $XCODE_MACOS_PROJ_DIR/$UPPERCASE_CONFIG/* $OUTPUT_DIR -} - -# Build xcframework -xcframework() -{ - if [[ ! $OSNAME = *"darwin"* ]]; then - exit 0 - fi - generate_projects # if needed - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - UPPERCASE_CONFIG="$(tr '[:lower:]' '[:upper:]' <<< ${1:0:1})${1:1}" - OUTPUT_DIR=$(pwd)/lib/$1 - cd $XCODE_MACOS_PROJ_DIR - xcodebuild -target zt -configuration "$UPPERCASE_CONFIG" -sdk "macosx" - - cd $XCODE_IOS_PROJ_DIR - xcodebuild -arch arm64 -target zt -configuration "$UPPERCASE_CONFIG" -sdk "iphoneos" - - cd $XCODE_IOS_SIMULATOR_PROJ_DIR - xcodebuild -target zt -configuration "$UPPERCASE_CONFIG" -sdk "iphonesimulator" - - mkdir -p $OUTPUT_DIR - - rm -rf $OUTPUT_DIR/zt.xcframework # Remove prior to move to prevent error - xcodebuild -create-xcframework \ - -framework $XCODE_MACOS_PROJ_DIR/$UPPERCASE_CONFIG/zt.framework \ - -framework $XCODE_IOS_PROJ_DIR/$UPPERCASE_CONFIG-iphoneos/zt.framework \ - -framework $XCODE_IOS_SIMULATOR_PROJ_DIR/$UPPERCASE_CONFIG-iphonesimulator/zt.framework \ - -output $OUTPUT_DIR/zt.xcframework -} - -# Build Java JAR for current host (uses JNI) -host_jar() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - copy_root_java_sources_to_projects - NORMALIZED_OSNAME=$OSNAME - if [[ $OSNAME = *"darwin"* ]]; then - DYNAMIC_LIB_NAME="libzt.dylib" - NORMALIZED_OSNAME="macos" - fi - if [[ $OSNAME = *"linux"* ]]; then - DYNAMIC_LIB_NAME="libzt.so" - fi - LIB_OUTPUT_DIR=$(pwd)/lib/$1/${NORMALIZED_OSNAME}-$(uname -m) - mkdir -p $LIB_OUTPUT_DIR - rm -rf $LIB_OUTPUT_DIR/zt.jar - # Build dynamic library - BUILD_DIR=$(pwd)/tmp/${NORMALIZED_OSNAME}-$(uname -m)-jni-$1 - UPPERCASE_CONFIG="$(tr '[:lower:]' '[:upper:]' <<< ${1:0:1})${1:1}" - $CMAKE -H. -B$BUILD_DIR -DCMAKE_BUILD_TYPE=$UPPERCASE_CONFIG -DSDK_JNI=ON "-DSDK_JNI=1" - $CMAKE --build $BUILD_DIR $BUILD_CONCURRENCY - # Copy dynamic library from previous build step - # And, remove any lib that may exist prior. We don't want accidental successes - cd $(pwd)/ports/java - rm $DYNAMIC_LIB_NAME - mv $BUILD_DIR/lib/$DYNAMIC_LIB_NAME . - # Begin constructing JAR - export JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8 - javac com/zerotier/libzt/*.java - jar cf zt.jar $DYNAMIC_LIB_NAME com/zerotier/libzt/*.class - rm $DYNAMIC_LIB_NAME - cd - - # Move completed JAR - LIB_OUTPUT_DIR=$(pwd)/lib/$1/${NORMALIZED_OSNAME}-$(uname -m) - mkdir -p $LIB_OUTPUT_DIR - mv $(pwd)/ports/java/zt.jar $LIB_OUTPUT_DIR - # Build sample app classes - # Remove old dynamic library if it exists - rm -rf $(pwd)/examples/java/$DYNAMIC_LIB_NAME - javac -cp ".:"$LIB_OUTPUT_DIR/zt.jar $(pwd)/examples/java/src/com/zerotier/libzt/javasimpleexample/*.java - # To run: - # jar xf $LIB_OUTPUT_DIR/zt.jar libzt.dylib - # cp libzt.dylib examples/java/ - # java -cp "lib/debug/macos-x86_64/zt.jar:examples/java/src/main/java" ExampleApp -} - -# Build all ordinary library types for current host -host_pinvoke() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - NORMALIZED_OSNAME=$OSNAME - if [[ $OSNAME = *"darwin"* ]]; then - DYNAMIC_LIB_NAME="libzt.dylib" - NORMALIZED_OSNAME="macos" - fi - if [[ $OSNAME = *"linux"* ]]; then - DYNAMIC_LIB_NAME="libzt.so" - fi - # CMake build files - BUILD_DIR=$(pwd)/tmp/${NORMALIZED_OSNAME}-$(uname -m)-$1 - mkdir -p $BUILD_DIR - # Where to place results - BIN_OUTPUT_DIR=$(pwd)/bin/$1/${NORMALIZED_OSNAME}-$(uname -m) - mkdir -p $BIN_OUTPUT_DIR - rm -rf $BIN_OUTPUT_DIR/* - LIB_OUTPUT_DIR=$(pwd)/lib/$1/${NORMALIZED_OSNAME}-$(uname -m)-pinvoke - mkdir -p $LIB_OUTPUT_DIR - rm -rf $LIB_OUTPUT_DIR/libzt.a $LIB_OUTPUT_DIR/$DYNAMIC_LIB_NAME $LIB_OUTPUT_DIR/libztcore.a - # Build - cmake -DZTS_PINVOKE=True -H. -B$BUILD_DIR -DCMAKE_BUILD_TYPE=$1 - $CMAKE --build $BUILD_DIR $BUILD_CONCURRENCY - # Move and clean up - cp -f $BUILD_DIR/bin/* $BIN_OUTPUT_DIR - cp -f $BUILD_DIR/lib/* $LIB_OUTPUT_DIR - clean_post_build -} - -# Build all ordinary library types for current host -host() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - NORMALIZED_OSNAME=$OSNAME - if [[ $OSNAME = *"darwin"* ]]; then - DYNAMIC_LIB_NAME="libzt.dylib" - NORMALIZED_OSNAME="macos" - fi - if [[ $OSNAME = *"linux"* ]]; then - DYNAMIC_LIB_NAME="libzt.so" - fi - # CMake build files - BUILD_DIR=$(pwd)/tmp/${NORMALIZED_OSNAME}-$(uname -m)-$1 - mkdir -p $BUILD_DIR - # Where to place results - BIN_OUTPUT_DIR=$(pwd)/bin/$1/${NORMALIZED_OSNAME}-$(uname -m) - mkdir -p $BIN_OUTPUT_DIR - rm -rf $BIN_OUTPUT_DIR/* - LIB_OUTPUT_DIR=$(pwd)/lib/$1/${NORMALIZED_OSNAME}-$(uname -m) - mkdir -p $LIB_OUTPUT_DIR - rm -rf $LIB_OUTPUT_DIR/libzt.a $LIB_OUTPUT_DIR/$DYNAMIC_LIB_NAME $LIB_OUTPUT_DIR/libztcore.a - # Build - $CMAKE -H. -B$BUILD_DIR -DCMAKE_BUILD_TYPE=$1 - $CMAKE --build $BUILD_DIR $BUILD_CONCURRENCY - # Move and clean up - cp -f $BUILD_DIR/bin/* $BIN_OUTPUT_DIR - cp -f $BUILD_DIR/lib/* $LIB_OUTPUT_DIR - clean_post_build -} - -# Set important variables for Android builds -set_android_env() -{ - # Set ANDROID_HOME because setting sdk.dir in local.properties isn't always reliable - #export PATH=/Library/Java/JavaVirtualMachines/$JDK/Contents/Home/bin/:${PATH} - #export PATH=/Users/$USER/Library/Android/sdk/platform-tools/:${PATH} - GRADLE_ARGS=--stacktrace - #ANDROID_APP_NAME=com.example.mynewestapplication - # for our purposes we limit this to execution on macOS - if [[ $OSNAME = *"linux"* ]]; then - export ANDROID_HOME=/usr/lib/android-sdk/ - fi - if [[ $OSNAME = *"darwin"* ]]; then - export ANDROID_HOME=/Users/$USER/Library/Android/sdk - fi -} - -# Build android AAR -android() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - # Unsure why, but Gradle's build script chokes on this non-source file now - rm -rf ext/ZeroTierOne/ext/miniupnpc/VERSION - export PATH=$ANDROID_HOME/cmdline-tools/tools/bin:$PATH - set_android_env - # Copy source files into project - cp -f src/bindings/java/*.java ${ANDROID_PROJ_DIR}/app/src/main/java/com/zerotier/libzt - # CMake build files - BUILD_DIR=$(pwd)/tmp/android-$1 - mkdir -p $BUILD_DIR - # If clean requested, remove temp build dir - if [[ $1 = *"clean"* ]]; then - rm -rf $BUILD_DIR - exit 0 - fi - # Where to place results - LIB_OUTPUT_DIR=$(pwd)/lib/$1/android - mkdir -p $LIB_OUTPUT_DIR - # Build - UPPERCASE_CONFIG="$(tr '[:lower:]' '[:upper:]' <<< ${1:0:1})${1:1}" - CMAKE_FLAGS="-DSDK_JNI=1 -DSDK_JNI=ON" - cd $ANDROID_PROJ_DIR - ./gradlew $GRADLE_ARGS assemble$UPPERCASE_CONFIG # assembleRelease / assembleDebug - mv $ANDROID_PROJ_DIR/app/build/outputs/aar/*.aar \ - $LIB_OUTPUT_DIR/libzt-$1.aar - cd - -} - -# Remove intermediate object files and/or libraries -clean_post_build() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - find $(pwd)/lib -type f -name 'liblwip_pic.a' -exec rm {} + - find $(pwd)/lib -type f -name 'liblwip.a' -exec rm {} + - find $(pwd)/lib -type f -name 'libminiupnpc.a' -exec rm {} + - find $(pwd)/lib -type f -name 'libminiupnpc_pic.a' -exec rm {} + - find $(pwd)/lib -type f -name 'libnatpmp.a' -exec rm {} + - find $(pwd)/lib -type f -name 'libnatpmp_pic.a' -exec rm {} + - find $(pwd)/lib -type f -name 'libzto_pic.a' -exec rm {} + - find $(pwd)/lib -type f -name 'libzt_pic.a' -exec rm {} + -} - -# General clean -clean() -{ - # Remove all temporary build files, products, etc - rm -rf builds tmp lib bin products - rm -f *.o *.s *.exp *.lib *.core core - # Generally search for and remove object files, libraries, etc - find . -path './*_products' -prune -type f \( -name '*.dylib' -o -name '*.dll' -o -name '*.so' -o -name \ - '*.a' -o -name '*.o' -o -name '*.exe' -o -name '*.o.d' -o -name \ - '*.out' -o -name '*.log' -o -name '*.dSYM' -o -name '*.class' \) -delete - # Remove any sources copied to project directories - rm -rf ports/android/app/src/main/java/com/zerotier/libzt/*.java - rm -rf ports/java/com/zerotier/libzt/*.java -} - -# Copy and rename Android AAR from lib to example app directory -prep_android_example() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - mkdir -p examples/android/ExampleAndroidApp/app/libs/ - cp -f lib/$1/android/libzt-$1.aar \ - examples/android/ExampleAndroidApp/app/libs/libzt.aar -} -# Clean Android project -clean_android_project() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - set_android_env - ANDROID_EXAMPLE_PROJ_DIR="examples/android/ExampleAndroidApp" - cd $ANDROID_EXAMPLE_PROJ_DIR - ./gradlew $GRADLE_ARGS clean - ./gradlew $GRADLE_ARGS cleanBuildCache - cd - -} -# Build APK from AAR and sources -build_android_app() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - set_android_env - ANDROID_EXAMPLE_PROJ_DIR="examples/android/ExampleAndroidApp" - UPPERCASE_CONFIG="$(tr '[:lower:]' '[:upper:]' <<< ${1:0:1})${1:1}" - cd $ANDROID_EXAMPLE_PROJ_DIR - ./gradlew assemble$UPPERCASE_CONFIG - cd - -} -# Stops an Android app that is already installed on device -stop_android_app() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - set_android_env - adb shell am force-stop $ANDROID_APP_NAME -} -# Starts an Android app that is already installed on device -start_android_app() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - set_android_env - adb shell monkey -p $ANDROID_APP_NAME 1 -} -# Copy and install example Android app on device -install_android_app() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - set_android_env - if [[ $1 = "release" ]]; then - APKNAME=app-$1-"unsigned" - else - APKNAME=app-$1 - fi - APK=examples/android/ExampleAndroidApp/app/build/outputs/apk/$1/$APKNAME.apk - echo "Installing $APK ..." - adb install -r $APK -} -# Perform all steps necessary to run a new instance of the app on device -run_android_app() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - stop_android_app - prep_android_example $1 - clean_android_project - # The following two functions take 'debug' as an argument regardless - # of the build type since the native code is built with the proper - # configuration anyway. - build_android_app "debug" - install_android_app "debug" - start_android_app -} -# View ADB logs of running Android app -android_app_log() -{ - set_android_env - if [[ $OSNAME = *"darwin"* ]]; then - adb logcat - fi -} -# View ADB logs of running Android app (filtered, must restart for each app re-launch) -android_app_log_filtered() -{ - set_android_env - if [[ $OSNAME = *"darwin"* ]]; then - adb logcat | grep -F "`adb shell ps | grep $ANDROID_APP_NAME | cut -c10-15`" - fi -} - - -# Copy java sources to projects before build process. This is so -# that we only have to maintain one set of sources for multiple java- -# based projects. -copy_root_java_sources_to_projects() -{ - cp -f src/bindings/java/*.java ports/java/com/zerotier/libzt/ -} - -# At the end of build stage, print contents and trees for inspection -display() -{ - find $(pwd)/lib -type f -name 'zt.jar' -exec echo -e "\n" \; -exec ls {} \; -exec jar tf {} + - echo -e "\n" - tree $(pwd)/lib -} - -# Merge all remotely-built targets. This is used before dist() -merge() -{ - if [ -d "darwin-x86_64_products" ]; then - rsync -a darwin-x86_64_products/ products/ - else - echo "Warning: darwin-x86_64_products is missing" - fi - # x86_64 64-bit linux - REMOTE_PRODUCTS_DIR=linux-x86_64_products - if [ -d "$REMOTE_PRODUCTS_DIR" ]; then - rsync -a $REMOTE_PRODUCTS_DIR/ products/ - echo "Merged products from " $REMOTE_PRODUCTS_DIR " to " products - else - echo "Warning: $REMOTE_PRODUCTS_DIR is missing" - fi - # armv7l linux - REMOTE_PRODUCTS_DIR=linux-armv7l_products - if [ -d "$REMOTE_PRODUCTS_DIR" ]; then - rsync -a $REMOTE_PRODUCTS_DIR/ products/ - echo "Merged products from " $REMOTE_PRODUCTS_DIR " to " products - else - echo "Warning: $REMOTE_PRODUCTS_DIR is missing" - fi - # armv6l linux - REMOTE_PRODUCTS_DIR=linux-armv6l_products - if [ -d "$REMOTE_PRODUCTS_DIR" ]; then - rsync -a $REMOTE_PRODUCTS_DIR/ products/ - echo "Merged products from " $REMOTE_PRODUCTS_DIR " to " products - else - echo "Warning: $REMOTE_PRODUCTS_DIR is missing" - fi - # 32/64-bit windows - REMOTE_PRODUCTS_DIR=win_products - if [ -d "$REMOTE_PRODUCTS_DIR" ]; then - rsync -a $REMOTE_PRODUCTS_DIR/ products/ - echo "Merged products from " $REMOTE_PRODUCTS_DIR " to " products - else - echo "Warning: $REMOTE_PRODUCTS_DIR is missing" - fi -} - -# On hosts which are not the final packaging platform (e.g. armv7, armv6l, etc) -# we will rename the products directory so that we can merge() it at a later -# stage on the packaging platform -wrap() -{ - ARCH_WRAP_DIR=$OSNAME"-"$(uname -m)_products - cp -rf lib $ARCH_WRAP_DIR - echo "Copied products to:" $ARCH_WRAP_DIR - PROD_FILENAME=$ARCH_WRAP_DIR.tar.gz - tar --exclude=$PROD_FILENAME -zcvf $PROD_FILENAME -C $ARCH_WRAP_DIR . -} - -# Renames and copies licenses for libzt and each of its dependencies -package_licenses() -{ - CURR_DIR=$1 - DEST_DIR=$2 - mkdir -p $DEST_DIR - cp $CURR_DIR/ext/lwip/COPYING $DEST_DIR/LWIP-LICENSE.BSD - cp $CURR_DIR/ext/concurrentqueue/LICENSE.md $DEST_DIR/CONCURRENTQUEUE-LICENSE.BSD - cp $CURR_DIR/LICENSE.txt $DEST_DIR/ZEROTIER-LICENSE.BSL-1.1 - cp $CURR_DIR/include/net/ROUTE_H-LICENSE.APSL $DEST_DIR/ROUTE_H-LICENSE.APSL - cp $CURR_DIR/include/net/ROUTE_H-LICENSE $DEST_DIR/ROUTE_H-LICENSE -} - -# Copies binaries, documentation, licenses, source, etc into a products -# directory and then tarballs everything together -package_everything() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - LIBZT_VERSION=$(git describe) - PROD_NAME=$LIBZT_VERSION-$(date '+%Y%m%d_%H-%M')-$1 - PROD_DIR=$(pwd)/products/$PROD_NAME/ - # Make products directory - # Licenses - package_licenses $(pwd) $PROD_DIR/licenses - # Examples - mkdir -p $PROD_DIR/examples - cp examples/cpp/* $PROD_DIR/examples - # Source - mkdir -p $PROD_DIR/src - cp src/*.cpp src/*.hpp src/*.c src/*.h $PROD_DIR/src - cp $(pwd)/README.pdf $PROD_DIR/README.pdf - # Header(s) - mkdir -p $PROD_DIR/include - cp $(pwd)/include/*.h $PROD_DIR/include - # Libraries - mkdir -p $PROD_DIR/lib - cp -r $(pwd)/products/$1/* $PROD_DIR/lib - rm -rf $(pwd)/products/$1 - # Clean - find $PROD_DIR -type f \( -name '*.DS_Store' -o -name 'thumbs.db' \) -delete - # Record the version (and each submodule's version) - echo "$(git describe)" > $PROD_DIR/VERSION - echo -e "$(git submodule status | awk '{$1=$1};1')" >> $PROD_DIR/VERSION - echo -e "$(cat ext/ZeroTierOne/version.h | grep ZEROTIER_ONE_VERSION | sed 's/\#define//g' | awk '{$1=$1};1')" >> $PROD_DIR/VERSION - echo "$(date)" >> $PROD_DIR/VERSION - # Tar everything - PROD_FILENAME=$(pwd)/products/$PROD_NAME.tar.gz - tar --exclude=$PROD_FILENAME -zcvf $PROD_FILENAME -C $PROD_DIR . - if [[ $OSNAME = *"darwin"* ]]; then - md5 $PROD_FILENAME - fi - if [[ $OSNAME = *"linux"* ]]; then - md5sum $PROD_FILENAME - fi - # Print results for post-build inspection - echo -e "\n" - tree $PROD_DIR - cat $PROD_DIR/VERSION - # Final check. Display warnings if anything is missing - FILES="VERSION - README.md - README.pdf - reference/errno.h - licenses/LWIP-LICENSE.BSD - licenses/CONCURRENTQUEUE-LICENSE.BSD - licenses/ZEROTIER-LICENSE.BSL-1.1 - licenses/ROUTE_H-LICENSE.APSL - licenses/ROUTE_H-LICENSE - licenses/LWIP-LICENSE.BSD" - for f in $FILES - do - if [ ! -f "$PROD_DIR$f" ]; then - echo "Warning: $PROD_DIR$f is missing" - fi - done -} - -# Generates a source-only tarball -sdist() -{ - VERSION=$(git describe --abbrev=0) - TARBALL_DIR="libzt-${VERSION}" - TARBALL_NAME=libzt-${VERSION}-source.tar.gz - PROD_DIR=$(pwd)/products/ - mkdir -p $PROD_DIR - # - mkdir ${TARBALL_DIR} - # primary sources - cp -rf src ${TARBALL_DIR}/src - cp -rf include ${TARBALL_DIR}/include - # important build scripts - cp Makefile ${TARBALL_DIR} - cp CMakeLists.txt ${TARBALL_DIR} - cp *.md ${TARBALL_DIR} - cp *.sh ${TARBALL_DIR} - cp *.bat ${TARBALL_DIR} - # submodules/dependencies - # lwIP - mkdir ${TARBALL_DIR}/ext - mkdir -p ${TARBALL_DIR}/ext/lwip/src - cp -rf ext/lwip/src/api ${TARBALL_DIR}/ext/lwip/src - cp -rf ext/lwip/src/core ${TARBALL_DIR}/ext/lwip/src - cp -rf ext/lwip/src/include ${TARBALL_DIR}/ext/lwip/src - cp -rf ext/lwip/src/netif ${TARBALL_DIR}/ext/lwip/src - # lwIP ports - mkdir -p ${TARBALL_DIR}/ext/lwip-contrib/ports - cp -rf ext/lwip-contrib/ports/unix ${TARBALL_DIR}/ext/lwip-contrib/ports - cp -rf ext/lwip-contrib/ports/win32 ${TARBALL_DIR}/ext/lwip-contrib/ports - # ZeroTierOne - mkdir ${TARBALL_DIR}/ext/ZeroTierOne - cp -rf ext/ZeroTierOne/*.h ${TARBALL_DIR}/ext/ZeroTierOne - cp -rf ext/ZeroTierOne/controller ${TARBALL_DIR}/ext/ZeroTierOne - cp -rf ext/ZeroTierOne/ext ${TARBALL_DIR}/ext/ZeroTierOne - cp -rf ext/ZeroTierOne/include ${TARBALL_DIR}/ext/ZeroTierOne - cp -rf ext/ZeroTierOne/node ${TARBALL_DIR}/ext/ZeroTierOne - cp -rf ext/ZeroTierOne/osdep ${TARBALL_DIR}/ext/ZeroTierOne - # - # Perform selective removal - rm -rf ${TARBALL_DIR}/ext/ZeroTierOne/ext/bin - rm -rf ${TARBALL_DIR}/ext/ZeroTierOne/ext/tap-mac - rm -rf ${TARBALL_DIR}/ext/ZeroTierOne/ext/librethinkdbxx - rm -rf ${TARBALL_DIR}/ext/ZeroTierOne/ext/installfiles - rm -rf ${TARBALL_DIR}/ext/ZeroTierOne/ext/curl-* - rm -rf ${TARBALL_DIR}/ext/ZeroTierOne/ext/http-parser - # - mkdir ${TARBALL_DIR}/ext/concurrentqueue - cp -rf ext/concurrentqueue/*.h ${TARBALL_DIR}/ext/concurrentqueue - # Licenses - package_licenses $(pwd) $TARBALL_DIR/licenses - # Tarball everything and display the results - tar -cvf ${TARBALL_NAME} ${TARBALL_DIR} - tree ${TARBALL_DIR} - rm -rf ${TARBALL_DIR} - mv ${TARBALL_NAME} ${PROD_DIR} -} - -# Package both debug and release -bdist() -{ - echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")" - package_everything "debug" - package_everything "release" -} - -# Generate a markdown CHANGELOG from git-log -update_changelog() -{ - first_commit=$(git rev-list --max-parents=0 HEAD) - git for-each-ref --sort=-refname --format="## [%(refname:short)] - %(taggerdate:short) &(newline)*** &(newline)- %(subject) %(body)" refs/tags > CHANGELOG.md - gsed -i '''s/\&(newline)/\n/' CHANGELOG.md # replace first instance - gsed -i '''s/\&(newline)/\n/' CHANGELOG.md # replace second instance - echo -e "\n" >> CHANGELOG.md - for curr_tag in $(git tag -l --sort=-v:refname) - do - prev_tag=$(git describe --abbrev=0 ${curr_tag}^) - if [ -z "${prev_tag}" ] - then - prev_tag=${first_commit} - fi - echo "[${curr_tag}]: https://github.com/zerotier/libzt/compare/${prev_tag}..${curr_tag}" >> CHANGELOG.md - done -} - -# List all functions in this script (just for convenience) -list() -{ - IFS=$'\n' - for f in $(declare -F); do - echo "${f:11}" - done -} - -"$@" diff --git a/examples/README.md b/examples/README.md index bcfb75f..759b5db 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,57 +1 @@ -Useful things to know - ===== - -### IDENTITIES and AUTHORIZATION: - -Upon the first execution of this code, a new identity will be generated and placed in the location given in the first argument to zts_start(path, ...). If you accidentally duplicate the identity files and use them simultaneously in a different node instance **you will experience undefined behavior** and it is likely that nothing will work. - -You must authorize the node ID provided by the `ZTS_EVENT_NODE_ONLINE` callback to join your network, otherwise nothing will happen. This can be done manually or via our web API: https://my.zerotier.com/help/api - -An exception to the above rule is if you are using an Ad-hoc network, it has no controller and therefore requires no authorization. - - -### ESTABLISHING A CONNECTION: - -Creating a standard socket connection generally works the same as it would using an ordinary socket interface, however with libzt there is a subtle difference in how connections are established which may cause confusion: - -The underlying virtual ZT layer creates what are called "transport-triggered links" between nodes. That is, links are not established until an attempt to communicate with a peer has taken place. The side effect is that the first few packets sent from a libzt instance are usually relayed via our free infrastructure and it isn't until a root server has passed contact information to both peers that a direct connection will be established. Therefore, it is required that multiple connection attempts be undertaken when initially communicating with a peer. After a transport-triggered link is established libzt will inform you via `ZTS_EVENT_PEER_DIRECT` for a specific peer ID. No action is required on your part for this callback event. - -*Note: In these initial moments before `ZTS_EVENT_PEER_DIRECT` has been received for a specific peer, traffic may be slow, jittery and there may be high packet loss. This will subside within a couple of seconds.* - - -### ERROR HANDLING: - -libzt's API is actually composed of two categories of functions with slightly different error reporting mechanisms. - -- Category 1: Control functions (`zts_start`, `zts_join`, `zts_get_peer_status`, etc). Errors returned by these functions can be any of the following: - -``` -ZTS_ERR_OK // No error -ZTS_ERR_SOCKET // Socket error, see zts_errno -ZTS_ERR_SERVICE // You probably did something at the wrong time -ZTS_ERR_ARG // Invalid argument -ZTS_ERR_NO_RESULT // No result (not necessarily an error) -ZTS_ERR_GENERAL // Consider filing a bug report -``` - -- Category 2: Sockets (`zts_socket`, `zts_bind`, `zts_connect`, `zts_listen`, etc). Errors returned by these functions can be the same as the above. With the added possibility of `zts_errno` being set. Much like standard errno this will provide a more specific reason for an error's occurrence. See `ZeroTierSockets.h` for values. - - -### API COMPATIBILITY WITH HOST OS: - -While the ZeroTier socket interface can coexist with your host OS's own interface in the same file with no type and naming conflicts, try not to mix and match host OS/libzt structures, functions, or constants. It may look similar and may even work some of the time but there enough differences that it will cause headaches: - -If you are calling a `zts_*` function, use the appropriate `ZTS_*` constants: - -``` -zts_socket(ZTS_AF_INET6, ZTS_SOCK_DGRAM, 0); (CORRECT) -zts_socket(AF_INET6, SOCK_DGRAM, 0); (INCORRECT) -``` - -If you are calling a `zts_*` function, use the appropriate `zts_*` structure: - -``` -struct zts_sockaddr_in in4; <------ Note the zts_* prefix - ... -zts_bind(fd, (struct zts_sockaddr *)&in4, sizeof(struct zts_sockaddr_in)) < 0) -``` \ No newline at end of file +Please read the [Common pitfalls](../include/README.md#common-pitfalls) section in the C API reference documentation. \ No newline at end of file From 7bc690046aac80c2673d27d1b7ea3171ace4e73b Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Wed, 3 Mar 2021 23:02:50 -0800 Subject: [PATCH 19/25] Add PyPI package (WIP) --- examples/python/example.py | 5 +- pkg/pypi/MANIFEST.in | 3 ++ pkg/pypi/README.md | 13 +++++ pkg/pypi/README.rst | 0 pkg/pypi/build.sh | 46 +++++++++++++++++ pkg/pypi/libzt/__init__.py | 4 ++ pkg/pypi/setup.cfg | 4 ++ pkg/pypi/setup.py | 103 +++++++++++++++++++++++++++++++++++++ 8 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 pkg/pypi/MANIFEST.in create mode 100644 pkg/pypi/README.md create mode 100644 pkg/pypi/README.rst create mode 100755 pkg/pypi/build.sh create mode 100644 pkg/pypi/libzt/__init__.py create mode 100644 pkg/pypi/setup.cfg create mode 100644 pkg/pypi/setup.py diff --git a/examples/python/example.py b/examples/python/example.py index 5a4a759..7e0de34 100644 --- a/examples/python/example.py +++ b/examples/python/example.py @@ -1,7 +1,6 @@ import time, sys import libzt -from prototype import * # Where identity files are stored keyPath = "." @@ -114,7 +113,7 @@ print('Joined network') if (mode == 'server'): print("Starting server...") try: - serv = zerotier.socket(libzt.ZTS_AF_INET6, libzt.ZTS_SOCK_STREAM, 0) + serv = libzt.zerotier.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) serv.bind(('::', serverPort)) serv.listen(5) while True: @@ -144,7 +143,7 @@ if (mode == 'server'): if (mode == 'client'): print("Starting client...") try: - client = zerotier.socket(libzt.ZTS_AF_INET6, libzt.ZTS_SOCK_STREAM, 0) + client = libzt.zerotier.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) print("connecting...") client.connect((remoteIP, serverPort)) print("send...") diff --git a/pkg/pypi/MANIFEST.in b/pkg/pypi/MANIFEST.in new file mode 100644 index 0000000..f57e51f --- /dev/null +++ b/pkg/pypi/MANIFEST.in @@ -0,0 +1,3 @@ +README.rst +setup.cfg +setup.py diff --git a/pkg/pypi/README.md b/pkg/pypi/README.md new file mode 100644 index 0000000..5a9f0d9 --- /dev/null +++ b/pkg/pypi/README.md @@ -0,0 +1,13 @@ +# PyPI Package ([pypi/libzt](https://pypi.python.org/pypi/libzt)) + +``` +pip install libzt +``` + +### Example usage + +- See [examples/python](../../examples/python) + +### Implementation Details + +- See [src/bindings/python](../../src/bindings/python) diff --git a/pkg/pypi/README.rst b/pkg/pypi/README.rst new file mode 100644 index 0000000..e69de29 diff --git a/pkg/pypi/build.sh b/pkg/pypi/build.sh new file mode 100755 index 0000000..df8527e --- /dev/null +++ b/pkg/pypi/build.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +PYBIN=python3 +#PYBIN=/opt/python/cp39-cp39/bin/python3 + +# Build the extension module +ext() +{ + # Symbolic link to source tree so that sdist structure makes sense + ln -s ../../ native + # Copy language bindings into module directory + cp -f native/src/bindings/python/*.py libzt/ + cp -f native/LICENSE.txt LICENSE + #mkdir -p build/temp.macosx-11-x86_64-3.9 + #mkdir -p build/temp.linux-x86_64-3.8 + # Build C libraries (and then) C++ extension + $PYBIN setup.py build_clib --verbose build_ext -i --verbose +} + +# Build a wheel +wheel() +{ + ext + $PYBIN setup.py bdist_wheel +} + +clean() +{ + find . -name '*.so' -type f -delete + find . -name '*.pyc' -type f -delete + find . -name '__pycache__' -type d -delete + rm -rf libzt/prototype.py + rm -rf libzt/libzt.py + rm -rf src ext build dist native + rm -rf libzt.egg-info + rm -rf LICENSE +} + +manylinux() +{ + CONTAINER="quay.io/pypa/manylinux_2_24_aarch64" + docker pull ${CONTAINER} + docker run --rm -it --entrypoint bash -v $(pwd)/../../:/media/libzt ${CONTAINER} +} + +"$@" \ No newline at end of file diff --git a/pkg/pypi/libzt/__init__.py b/pkg/pypi/libzt/__init__.py new file mode 100644 index 0000000..3b8b2fe --- /dev/null +++ b/pkg/pypi/libzt/__init__.py @@ -0,0 +1,4 @@ +__version__ = "1.3.3" + +from .libzt import * +from .prototype import ztsocket, zerotier \ No newline at end of file diff --git a/pkg/pypi/setup.cfg b/pkg/pypi/setup.cfg new file mode 100644 index 0000000..447a470 --- /dev/null +++ b/pkg/pypi/setup.cfg @@ -0,0 +1,4 @@ +[metadata] +version = attr: libzt.__version__ +description-file = README.rst +license_files = LICENSE \ No newline at end of file diff --git a/pkg/pypi/setup.py b/pkg/pypi/setup.py new file mode 100644 index 0000000..33155dc --- /dev/null +++ b/pkg/pypi/setup.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python + +from setuptools import setup, Extension, Command, Distribution +import glob +import os + +class BinaryDistribution(Distribution): + def is_pure(self): + return False + +cpp_glob = [] +c_glob = [] + +# Windows +if os.name == 'nt': + print('TODO') + #extra_compile_args=['/std:c++14', '-DNOMINMAX=1', '-DZT_SDK', '-DSDK'], + #extra_link_args=['/LIBPATH:.', 'WS2_32.Lib', 'ShLwApi.Lib', 'iphlpapi.Lib','lwip.lib'], + +# Everything else +else: + cpp_glob.extend(list(glob.glob('native/src/bindings/python/*.cpp'))) + cpp_glob.extend(list(glob.glob('native/src/*.cpp'))) + cpp_glob.extend(list(glob.glob('native/ext/ZeroTierOne/node/*.cpp'))) + cpp_glob.extend(list(glob.glob('native/ext/ZeroTierOne/osdep/OSUtils.cpp'))) + cpp_glob.extend(list(glob.glob('native/ext/ZeroTierOne/osdep/PortMapper.cpp'))) + cpp_glob.extend(list(glob.glob('native/ext/ZeroTierOne/osdep/ManagedRoute.cpp'))) + + my_include_dirs=['native/include', + 'native/src', + 'native/src/bindings/python', + 'native/ext/concurrentqueue', + 'native/ext/lwip/src/include', + 'native/ext/lwip-contrib/ports/unix/port/include', + 'native/ext/ZeroTierOne/include', + 'native/ext/ZeroTierOne/node', + 'native/ext/ZeroTierOne/service', + 'native/ext/ZeroTierOne/osdep', + 'native/ext/ZeroTierOne/controller'] + + libzt_module = Extension('libzt._libzt', + extra_compile_args=['-std=c++11', '-DZTS_ENABLE_PYTHON=1', '-DZT_SDK'], + sources=cpp_glob, include_dirs=my_include_dirs) + + # Separate C library, this is needed since C++ compiler flags are applied + # to everything in the extension module regardless of type. + + # libnatpmp + c_glob.extend(list(glob.glob('native/ext/ZeroTierOne/ext/libnatpmp/natpmp.c'))) + c_glob.extend(list(glob.glob('native/ext/ZeroTierOne/ext/libnatpmp/wingettimeofday.c'))) + c_glob.extend(list(glob.glob('native/ext/ZeroTierOne/ext/libnatpmp/getgateway.c'))) + # miniupnpc + c_glob.extend(list(glob.glob('native/ext/miniupnpc/*.c'))) + # lwip + c_glob.extend(list(glob.glob('native/ext/lwip/src/netif/*.c'))) + c_glob.extend(list(glob.glob('native/ext/lwip/src/api/*.c'))) + c_glob.extend(list(glob.glob('native/ext/lwip/src/core/*.c'))) + c_glob.extend(list(glob.glob('native/ext/lwip/src/core/ipv4/*.c'))) + c_glob.extend(list(glob.glob('native/ext/lwip/src/core/ipv6/*.c'))) + c_glob.extend(list(glob.glob('native/ext/lwip/src/netif/*.c'))) + c_glob.extend(list(glob.glob('native/ext/lwip-contrib/ports/unix/port/sys_arch.c'))) + +cstuff = ('cstuff', {'sources': + c_glob, 'include_dirs': my_include_dirs}) + +setup( + name = 'libzt', + version = '1.3.3', + description = 'ZeroTier', + long_description = 'Encrypted P2P communication between apps and services', + author = 'ZeroTier, Inc.', + author_email = 'joseph.henry@zerotier.com', + url = 'https://github.com/zerotier/libzt', + license='BUSL 1.1', + download_url = 'https://github.com/zerotier/libzt/archive/1.3.3.tar.gz', + keywords = 'zerotier sdwan sdn virtual network socket p2p peer-to-peer', + py_modules = ['libzt'], + packages = ['libzt'], + classifiers = ['Development Status :: 3 - Alpha', + 'Topic :: Internet', + 'Topic :: System :: Networking', + 'Topic :: Security :: Cryptography', + 'Operating System :: OS Independent', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: Science/Research', + 'Intended Audience :: System Administrators', + 'Intended Audience :: Telecommunications Industry', + 'Intended Audience :: End Users/Desktop', + 'License :: Free for non-commercial use', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX :: BSD', + 'Operating System :: Unix', + 'Programming Language :: C++', + 'Programming Language :: C', + 'Programming Language :: Python' + ], + distclass=BinaryDistribution, + libraries=[cstuff], + ext_modules = [libzt_module], + python_requires='>=3.0', +) \ No newline at end of file From 43ebeb2760bc0b148be96183c816f6ff43250332 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Fri, 5 Mar 2021 00:07:54 -0800 Subject: [PATCH 20/25] Update various READMEs --- README.md | 2 +- pkg/README.md | 11 +++++++++++ pkg/pypi/README.md | 20 ++++++++++++-------- pkg/pypi/README.rst | 0 4 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 pkg/README.md delete mode 100644 pkg/pypi/README.rst diff --git a/README.md b/README.md index e858668..4f00b85 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Peer-to-peer and cross-platform encrypted connections built right into your app
-latest libzt version +latest libzt version Last Commit Build Status (master branch)
diff --git a/pkg/README.md b/pkg/README.md new file mode 100644 index 0000000..8d43f0e --- /dev/null +++ b/pkg/README.md @@ -0,0 +1,11 @@ +# Packages + +This is where package spec files, source trees, project configs, etc live. + +## Example usage + +- See [examples/](../../examples/) + +## Implementation Details + +- See [src/bindings/](../../src/bindings/) diff --git a/pkg/pypi/README.md b/pkg/pypi/README.md index 5a9f0d9..314a337 100644 --- a/pkg/pypi/README.md +++ b/pkg/pypi/README.md @@ -1,13 +1,17 @@ -# PyPI Package ([pypi/libzt](https://pypi.python.org/pypi/libzt)) +
-``` -pip install libzt -``` + -### Example usage +

libzt (ZeroTier)

+Peer-to-peer and cross-platform encrypted connections built right into your app or service. No drivers, no root, and no host configuration. -- See [examples/python](../../examples/python) +latest libzt version +Last Commit +Build Status (master branch) +
-### Implementation Details +
-- See [src/bindings/python](../../src/bindings/python) +Examples, tutorials and API docs for Python and other languages: [github.com/zerotier/libzt](https://www.github.com/zerotier/libzt) + +*NOTE: The implementation of this language binding attempts to be as Pythonic as possible. If something is not as it should be, please make a pull request or issue. Thanks.* diff --git a/pkg/pypi/README.rst b/pkg/pypi/README.rst deleted file mode 100644 index e69de29..0000000 From 5072d993b1eca27766e99a614f3f5fbccb9ffd50 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Fri, 5 Mar 2021 00:18:11 -0800 Subject: [PATCH 21/25] Implement more of the Python language binding. Minor adjustments to PyPI package --- examples/python/example.py | 21 +- include/ZeroTierSockets.h | 2 + pkg/pypi/MANIFEST.in | 2 +- pkg/pypi/libzt/__init__.py | 2 +- pkg/pypi/setup.cfg | 2 +- pkg/pypi/setup.py | 27 +- src/bindings/python/PythonSockets.cpp | 26 +- src/bindings/python/prototype.py | 103 -------- src/bindings/python/sockets.py | 347 ++++++++++++++++++++++++++ 9 files changed, 404 insertions(+), 128 deletions(-) delete mode 100644 src/bindings/python/prototype.py create mode 100644 src/bindings/python/sockets.py diff --git a/examples/python/example.py b/examples/python/example.py index 7e0de34..e00ab35 100644 --- a/examples/python/example.py +++ b/examples/python/example.py @@ -66,7 +66,7 @@ print('serverPort = ', serverPort) # # Event handler # -class MyEventCallbackClass(libzt.PythonDirectorCallbackClass): +class MyEventCallbackClass(libzt.EventCallbackClass): def on_zerotier_event(self, msg): global is_online global is_joined @@ -95,12 +95,12 @@ class MyEventCallbackClass(libzt.PythonDirectorCallbackClass): # print("Starting ZeroTier..."); eventCallback = MyEventCallbackClass() -libzt.zts_start(keyPath, eventCallback, ztServicePort) +libzt.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) +libzt.join(networkId) while (not is_joined): time.sleep(1) # You can ping this app at this point print('Joined network') @@ -112,22 +112,23 @@ print('Joined network') # if (mode == 'server'): print("Starting server...") + serv = libzt.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) try: - serv = libzt.zerotier.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) + #serv.setblocking(True) serv.bind(('::', serverPort)) serv.listen(5) while True: conn, addr = serv.accept() print('Accepted connection from: ', addr) while True: - print('recv()...') + print('recv:') data = conn.recv(4096) if data: print('data = ', data) #print(type(b'what')) #exit(0) if not data: break - print('send()...') + print('send:') #bytes(data, 'ascii') + b'\x00' n_bytes = conn.send(data) # echo back to the server print('sent ' + str(n_bytes) + ' byte(s)') @@ -135,6 +136,7 @@ if (mode == 'server'): print('client disconnected') except Exception as e: print(e) + print('errno=',libzt.errno()) # See include/ZeroTierSockets.h for codes # @@ -142,17 +144,16 @@ if (mode == 'server'): # if (mode == 'client'): print("Starting client...") + client = libzt.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) try: - client = libzt.zerotier.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) print("connecting...") client.connect((remoteIP, serverPort)) - print("send...") + print("send:") data = 'Hello, world!' client.send(data) - print("rx...") data = client.recv(1024) - print('Received', repr(data)) except Exception as e: print(e) + print('errno=',libzt.errno()) diff --git a/include/ZeroTierSockets.h b/include/ZeroTierSockets.h index 917c943..0b25342 100644 --- a/include/ZeroTierSockets.h +++ b/include/ZeroTierSockets.h @@ -876,6 +876,8 @@ int zts_py_listen(int fd, int backlog); PyObject * zts_py_recv(int fd, int len, int flags); int zts_py_send(int fd, PyObject *buf, int len, int flags); int zts_py_close(int fd); +int zts_py_setblocking(int fd, int flag); +int zts_py_getblocking(int fd); #endif // ZTS_ENABLE_PYTHON diff --git a/pkg/pypi/MANIFEST.in b/pkg/pypi/MANIFEST.in index f57e51f..bfa060a 100644 --- a/pkg/pypi/MANIFEST.in +++ b/pkg/pypi/MANIFEST.in @@ -1,3 +1,3 @@ -README.rst +README.md setup.cfg setup.py diff --git a/pkg/pypi/libzt/__init__.py b/pkg/pypi/libzt/__init__.py index 3b8b2fe..9c0487c 100644 --- a/pkg/pypi/libzt/__init__.py +++ b/pkg/pypi/libzt/__init__.py @@ -1,4 +1,4 @@ __version__ = "1.3.3" from .libzt import * -from .prototype import ztsocket, zerotier \ No newline at end of file +from .sockets import * diff --git a/pkg/pypi/setup.cfg b/pkg/pypi/setup.cfg index 447a470..36b1ca0 100644 --- a/pkg/pypi/setup.cfg +++ b/pkg/pypi/setup.cfg @@ -1,4 +1,4 @@ [metadata] version = attr: libzt.__version__ -description-file = README.rst +description-file = README.md license_files = LICENSE \ No newline at end of file diff --git a/pkg/pypi/setup.py b/pkg/pypi/setup.py index 33155dc..2af77a6 100644 --- a/pkg/pypi/setup.py +++ b/pkg/pypi/setup.py @@ -4,6 +4,11 @@ from setuptools import setup, Extension, Command, Distribution import glob import os +from os import path +this_directory = path.abspath(path.dirname(__file__)) +with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + class BinaryDistribution(Distribution): def is_pure(self): return False @@ -67,16 +72,18 @@ setup( name = 'libzt', version = '1.3.3', description = 'ZeroTier', - long_description = 'Encrypted P2P communication between apps and services', +# long_description = 'Encrypted P2P communication between apps and services', + long_description=long_description, + long_description_content_type='text/markdown', author = 'ZeroTier, Inc.', - author_email = 'joseph.henry@zerotier.com', + author_email = 'joseph@zerotier.com', url = 'https://github.com/zerotier/libzt', license='BUSL 1.1', - download_url = 'https://github.com/zerotier/libzt/archive/1.3.3.tar.gz', - keywords = 'zerotier sdwan sdn virtual network socket p2p peer-to-peer', + download_url = 'https://github.com/zerotier/libzt/releases', + keywords = 'zerotier p2p peer-to-peer sdwan sdn virtual network socket tcp udp zt encryption encrypted', py_modules = ['libzt'], packages = ['libzt'], - classifiers = ['Development Status :: 3 - Alpha', + classifiers = ['Development Status :: 4 - Beta', 'Topic :: Internet', 'Topic :: System :: Networking', 'Topic :: Security :: Cryptography', @@ -88,13 +95,17 @@ setup( 'Intended Audience :: Telecommunications Industry', 'Intended Audience :: End Users/Desktop', 'License :: Free for non-commercial use', - 'Operating System :: MacOS :: MacOS X', + 'Operating System :: MacOS', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: BSD', + 'Operating System :: POSIX :: Linux', 'Operating System :: Unix', - 'Programming Language :: C++', 'Programming Language :: C', - 'Programming Language :: Python' + 'Programming Language :: C++', + 'Programming Language :: Python', + 'Programming Language :: Java', + 'Programming Language :: C#', + 'Programming Language :: Rust' ], distclass=BinaryDistribution, libraries=[cstuff], diff --git a/src/bindings/python/PythonSockets.cpp b/src/bindings/python/PythonSockets.cpp index 1741168..dc7aaf8 100644 --- a/src/bindings/python/PythonSockets.cpp +++ b/src/bindings/python/PythonSockets.cpp @@ -22,7 +22,25 @@ #ifdef ZTS_ENABLE_PYTHON -static int tuple_to_sockaddr(int family, +int zts_py_setblocking(int fd, int flag) +{ + int flags = ZTS_ERR_OK; + if ((flags = zts_fcntl(fd, F_GETFL, 0)) < 0) { + return ZTS_ERR_SOCKET; + } + return zts_fcntl(fd, F_SETFL, flags | ZTS_O_NONBLOCK); +} + +int zts_py_getblocking(int fd) +{ + int flags = ZTS_ERR_OK; + if ((flags = zts_fcntl(fd, F_GETFL, 0)) < 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) { if (family == AF_INET) { @@ -33,7 +51,7 @@ static int tuple_to_sockaddr(int family, return ZTS_ERR_ARG; } if (!PyArg_ParseTuple(addr_obj, - "eti:tuple_to_sockaddr", "idna", &host_str, &port)) { + "eti:zts_py_tuple_to_sockaddr", "idna", &host_str, &port)) { return ZTS_ERR_ARG; } addr = (struct zts_sockaddr_in*)dst_addr; @@ -84,7 +102,7 @@ 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, + if (zts_py_tuple_to_sockaddr(family, addr_obj, (struct zts_sockaddr *)&addrbuf, &addrlen) != ZTS_ERR_OK) { return ZTS_ERR_ARG; @@ -101,7 +119,7 @@ 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, + if (zts_py_tuple_to_sockaddr(family, addr_obj, (struct zts_sockaddr *)&addrbuf, &addrlen) != ZTS_ERR_OK) { return ZTS_ERR_ARG; diff --git a/src/bindings/python/prototype.py b/src/bindings/python/prototype.py deleted file mode 100644 index bac71ff..0000000 --- a/src/bindings/python/prototype.py +++ /dev/null @@ -1,103 +0,0 @@ -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) diff --git a/src/bindings/python/sockets.py b/src/bindings/python/sockets.py new file mode 100644 index 0000000..ac510fd --- /dev/null +++ b/src/bindings/python/sockets.py @@ -0,0 +1,347 @@ +import libzt + +class EventCallbackClass(libzt.PythonDirectorCallbackClass): + pass + +# 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) + ')') + +# 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 libzt.cvar.zts_errno + +# Start the ZeroTier service +def start(path, callback, port): + libzt.zts_start(path, callback, port) + +# Stop the ZeroTier service +def stop(): + libzt.zts_stop() + +# [debug] Restarts the ZeroTier service and network stack +def restart(): + libzt.zts_restart() + +# Permenantly shuts down the network stack. +def free(): + libzt.zts_free() + +# Join a ZeroTier network +def join(networkId): + libzt.zts_join(networkId) + +# Leave a ZeroTier network +def leave(networkId): + libzt.zts_leave(networkId) + +# Orbit a moon +def zts_orbit(moonWorldId, moonSeed): + return libzt.zts_orbit(moonWorldId, moonSeed) + +# De-orbit a moon +def zts_deorbit(moonWorldId): + return libzt.zts_deorbit(moonWorldId) + +# Pythonic class that wraps low-level sockets +class socket(): + + _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 + + @property + def proto(self): + return _proto + + # Intentionally not supported + def socketpair(self, family, type, proto): + raise NotImplementedError("socketpair(): libzt does not support AF_UNIX sockets") + + # Convenience function to create a connection to a remote host + def create_connection(self, remote_address): + # TODO: implement timeout + conn = socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) + conn.connect(remote_address) + return conn + + # Convenience function to create a listening socket + def create_server(self, local_address, family=libzt.ZTS_AF_INET, backlog=None): + # TODO: implement reuse_port + conn = socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) + conn.bind(local_address) + conn.listen(backlog) + return conn + + def fromfd(self, fd, family, type, proto=0): + raise NotImplementedError("fromfd(): Not supported. OS File descriptors aren't used in libzt.") + + def fromshare(self, data): + raise NotImplementedError("libzt does not support this (yet?)") + + def close(self, fd): + raise NotImplementedError("close(fd): Not supported OS File descriptors aren't used in libzt.") + + def getaddrinfo(self, host, port, family=0, type=0, proto=0, flags=0): + raise NotImplementedError("libzt does not support this (yet?)") + + def getfqdn(self, name): + raise NotImplementedError("libzt does not support this (yet?)") + + def gethostbyname(self, hostname): + raise NotImplementedError("libzt does not support this (yet?)") + + def gethostbyname_ex(self, hostname): + raise NotImplementedError("libzt does not support this (yet?)") + + def gethostname(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def gethostbyaddr(self, ip_address): + raise NotImplementedError("libzt does not support this (yet?)") + + def getnameinfo(self, sockaddr, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def getprotobyname(self, protocolname): + raise NotImplementedError("libzt does not support this (yet?)") + + def getservbyname(self, servicename, protocolname): + raise NotImplementedError("libzt does not support this (yet?)") + + def getservbyport(self, port, protocolname): + raise NotImplementedError("libzt does not support this (yet?)") + + def ntohl(x): + raise NotImplementedError("libzt does not support this (yet?)") + + def ntohs(x): + raise NotImplementedError("libzt does not support this (yet?)") + + def htonl(x): + raise NotImplementedError("libzt does not support this (yet?)") + + def htons(x): + raise NotImplementedError("libzt does not support this (yet?)") + + def inet_aton(ip_string): + raise NotImplementedError("libzt does not support this (yet?)") + + def inet_ntoa(packed_ip): + raise NotImplementedError("libzt does not support this (yet?)") + + def inet_pton(address_family, ip_string): + raise NotImplementedError("libzt does not support this (yet?)") + + def inet_ntop(address_family, packed_ip): + raise NotImplementedError("libzt does not support this (yet?)") + + def CMSG_LEN(length): + raise NotImplementedError("libzt does not support this (yet?)") + + def CMSG_SPACE(length): + raise NotImplementedError("libzt does not support this (yet?)") + + def getdefaulttimeout(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def setdefaulttimeout(self, timeout): + raise NotImplementedError("libzt does not support this (yet?)") + + def sethostname(self, name): + raise NotImplementedError("libzt does not support this (yet?)") + + def if_nameindex(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def if_nametoindex(self, if_name): + raise NotImplementedError("if_nametoindex(): libzt does not name interfaces.") + + def if_indextoname(self, if_index): + raise NotImplementedError("if_indextoname(): libzt does not name interfaces.") + + # Accept connection on the socket + def accept(self): + new_conn_fd, addr, port = libzt.zts_py_accept(self._fd) + if (new_conn_fd < 0): + handle_error(new_conn_fd) + return None + return ztsocket(self._family, self._type, self._proto, new_conn_fd), addr + + # 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): + handle_error(err) + + # Close the socket + def close(self): + err = libzt.zts_py_close(self._fd) + if (err < 0): + 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): + handle_error(err) + + # Connect to remote host but return low-level result code, and errno on failure + # This uses a non-thread-safe implementation of errno + def connect_ex(self, remote_address): + err = libzt.zts_py_connect(self._fd, self._family, self._type, remote_address) + if (err < 0): + return errno() + return err + + def detach(self): + raise NotImplementedError("detach(): Not supported. OS File descriptors aren't used in libzt.") + + def dup(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def fileno(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def get_inheritable(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def getpeername(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def getsockname(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def getsockopt(self, level, optname, buflen): + raise NotImplementedError("libzt does not support this (yet?)") + + # Get whether this socket is in blocking or non-blocking mode + def getblocking(self): + return libzt.zts_py_getblocking(self._fd) + + def gettimeout(self): + raise NotImplementedError("libzt does not support this (yet?)") + + def ioctl(self, control, option): + raise NotImplementedError("libzt does not support this (yet?)") + + # 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): + handle_error(err) + + def makefile(mode='r', buffering=None, *, encoding=None, errors=None, newline=None): + raise NotImplementedError("libzt does not support this (yet?)") + + # 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): + handle_error(err) + return None + return data + + def recvfrom(self, bufsize, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def recvmsg(self, bufsize, ancbufsize, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def recvmsg_into(self, buffers, ancbufsize, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def recvfrom_into(self, buffer, nbytes, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def recv_into(self, buffer, nbytes, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + # 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): + handle_error(err) + return err + + def sendall(self, bytes, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def sendto(self, bytes, address): + raise NotImplementedError("libzt does not support this (yet?)") + + def sendto(self, bytes, flags, address): + raise NotImplementedError("libzt does not support this (yet?)") + + def sendmsg(self, buffers, ancdata, flags, address): + raise NotImplementedError("libzt does not support this (yet?)") + + def sendmsg_afalg(self, msg, *, op, iv, assoclen, flags): + raise NotImplementedError("sendmsg_afalg(): libzt does not support AF_ALG") + + def send_fds(self, sock, buffers, fds, flags, address): + raise NotImplementedError("libzt does not support this (yet?)") + + def recv_fds(self, sock, bufsize, maxfds, flags): + raise NotImplementedError("libzt does not support this (yet?)") + + def sendfile(self, file, offset=0, count=None): + raise NotImplementedError("libzt does not support this (yet?)") + + def set_inheritable(self, inheritable): + raise NotImplementedError("libzt does not support this (yet?)") + + # Set whether this socket is in blocking or non-blocking mode + def setblocking(self, flag): + libzt.zts_py_setblocking(self._fd, flag) + + def settimeout(self, value): + raise NotImplementedError("libzt does not support this (yet?)") + + def setsockopt(self, level, optname, value): + # TODO: value: buffer + # TODO: value: int + # TODO: value: None -> optlen required + raise NotImplementedError("libzt does not support this (yet?)") + + # Shut down one or more aspects (rx/tx) of the socket + def shutdown(self, how): + libzt.shutdown(self._fd, how) From e1d0f92d617cfe551a83ec02832246c18449a095 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Fri, 5 Mar 2021 01:15:34 -0800 Subject: [PATCH 22/25] Remove Android example. The Java example should suffice --- examples/android/ExampleAndroidApp/.gitignore | 10 - examples/android/ExampleAndroidApp/.project | 17 -- .../org.eclipse.buildship.core.prefs | 2 - .../android/ExampleAndroidApp/app/.classpath | 6 - .../android/ExampleAndroidApp/app/.gitignore | 1 - .../android/ExampleAndroidApp/app/.project | 23 --- .../org.eclipse.buildship.core.prefs | 2 - .../ExampleAndroidApp/app/build.gradle | 43 ----- .../ExampleAndroidApp/app/proguard-rules.pro | 21 --- .../ExampleInstrumentedTest.java | 26 --- .../app/src/main/AndroidManifest.xml | 24 --- .../example/exampleandroidapp/HTTPWorker.java | 77 -------- .../exampleandroidapp/MainActivity.java | 96 ---------- .../MyZeroTierEventListener.java | 83 --------- .../exampleandroidapp/ExampleUnitTest.java | 17 -- .../android/ExampleAndroidApp/build.gradle | 27 --- .../ExampleAndroidApp/gradle.properties | 13 -- .../gradle/wrapper/gradle-wrapper.properties | 6 - examples/android/ExampleAndroidApp/gradlew | 172 ------------------ .../android/ExampleAndroidApp/gradlew.bat | 84 --------- .../android/ExampleAndroidApp/settings.gradle | 1 - 21 files changed, 751 deletions(-) delete mode 100644 examples/android/ExampleAndroidApp/.gitignore delete mode 100644 examples/android/ExampleAndroidApp/.project delete mode 100644 examples/android/ExampleAndroidApp/.settings/org.eclipse.buildship.core.prefs delete mode 100644 examples/android/ExampleAndroidApp/app/.classpath delete mode 100644 examples/android/ExampleAndroidApp/app/.gitignore delete mode 100644 examples/android/ExampleAndroidApp/app/.project delete mode 100644 examples/android/ExampleAndroidApp/app/.settings/org.eclipse.buildship.core.prefs delete mode 100644 examples/android/ExampleAndroidApp/app/build.gradle delete mode 100644 examples/android/ExampleAndroidApp/app/proguard-rules.pro delete mode 100644 examples/android/ExampleAndroidApp/app/src/androidTest/java/com/example/exampleandroidapp/ExampleInstrumentedTest.java delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/AndroidManifest.xml delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/java/com/example/exampleandroidapp/HTTPWorker.java delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/java/com/example/exampleandroidapp/MainActivity.java delete mode 100644 examples/android/ExampleAndroidApp/app/src/main/java/com/example/exampleandroidapp/MyZeroTierEventListener.java delete mode 100644 examples/android/ExampleAndroidApp/app/src/test/java/com/example/exampleandroidapp/ExampleUnitTest.java delete mode 100644 examples/android/ExampleAndroidApp/build.gradle delete mode 100644 examples/android/ExampleAndroidApp/gradle.properties delete mode 100644 examples/android/ExampleAndroidApp/gradle/wrapper/gradle-wrapper.properties delete mode 100755 examples/android/ExampleAndroidApp/gradlew delete mode 100644 examples/android/ExampleAndroidApp/gradlew.bat delete mode 100644 examples/android/ExampleAndroidApp/settings.gradle diff --git a/examples/android/ExampleAndroidApp/.gitignore b/examples/android/ExampleAndroidApp/.gitignore deleted file mode 100644 index 5edb4ee..0000000 --- a/examples/android/ExampleAndroidApp/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -.DS_Store -/build -/captures -.externalNativeBuild diff --git a/examples/android/ExampleAndroidApp/.project b/examples/android/ExampleAndroidApp/.project deleted file mode 100644 index 552deba..0000000 --- a/examples/android/ExampleAndroidApp/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - ExampleAndroidApp - Project ExampleAndroidApp created by Buildship. - - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - - org.eclipse.buildship.core.gradleprojectnature - - diff --git a/examples/android/ExampleAndroidApp/.settings/org.eclipse.buildship.core.prefs b/examples/android/ExampleAndroidApp/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index e889521..0000000 --- a/examples/android/ExampleAndroidApp/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -connection.project.dir= -eclipse.preferences.version=1 diff --git a/examples/android/ExampleAndroidApp/app/.classpath b/examples/android/ExampleAndroidApp/app/.classpath deleted file mode 100644 index 7c0adb0..0000000 --- a/examples/android/ExampleAndroidApp/app/.classpath +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/android/ExampleAndroidApp/app/.gitignore b/examples/android/ExampleAndroidApp/app/.gitignore deleted file mode 100644 index 796b96d..0000000 --- a/examples/android/ExampleAndroidApp/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/examples/android/ExampleAndroidApp/app/.project b/examples/android/ExampleAndroidApp/app/.project deleted file mode 100644 index ac485d7..0000000 --- a/examples/android/ExampleAndroidApp/app/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - app - Project app created by Buildship. - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.buildship.core.gradleprojectnature - - diff --git a/examples/android/ExampleAndroidApp/app/.settings/org.eclipse.buildship.core.prefs b/examples/android/ExampleAndroidApp/app/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index b1886ad..0000000 --- a/examples/android/ExampleAndroidApp/app/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -connection.project.dir=.. -eclipse.preferences.version=1 diff --git a/examples/android/ExampleAndroidApp/app/build.gradle b/examples/android/ExampleAndroidApp/app/build.gradle deleted file mode 100644 index 4541c76..0000000 --- a/examples/android/ExampleAndroidApp/app/build.gradle +++ /dev/null @@ -1,43 +0,0 @@ -apply plugin: 'com.android.application' - -android { - compileSdkVersion 28 - defaultConfig { - applicationId "com.example.mynewestapplication" - minSdkVersion 21 - targetSdkVersion 28 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - externalNativeBuild { - cmake { - cppFlags "" - } - } - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - release { - debuggable true - jniDebuggable true - minifyEnabled false - } - } -} - -dependencies { - implementation files('libs/libzt.aar') - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:28.0.0-alpha3' - implementation("com.squareup.okhttp3:okhttp:3.12.0") - implementation 'com.android.support.constraint:constraint-layout:1.1.2' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' - - implementation 'com.github.bumptech.glide:glide:4.6.1' - annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1' -} diff --git a/examples/android/ExampleAndroidApp/app/proguard-rules.pro b/examples/android/ExampleAndroidApp/app/proguard-rules.pro deleted file mode 100644 index f1b4245..0000000 --- a/examples/android/ExampleAndroidApp/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/examples/android/ExampleAndroidApp/app/src/androidTest/java/com/example/exampleandroidapp/ExampleInstrumentedTest.java b/examples/android/ExampleAndroidApp/app/src/androidTest/java/com/example/exampleandroidapp/ExampleInstrumentedTest.java deleted file mode 100644 index e8ec7c8..0000000 --- a/examples/android/ExampleAndroidApp/app/src/androidTest/java/com/example/exampleandroidapp/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.example.exampleandroidapp; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - assertEquals("com.example.exampleandroidapp", appContext.getPackageName()); - } -} diff --git a/examples/android/ExampleAndroidApp/app/src/main/AndroidManifest.xml b/examples/android/ExampleAndroidApp/app/src/main/AndroidManifest.xml deleted file mode 100644 index 7bc5ffd..0000000 --- a/examples/android/ExampleAndroidApp/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/android/ExampleAndroidApp/app/src/main/java/com/example/exampleandroidapp/HTTPWorker.java b/examples/android/ExampleAndroidApp/app/src/main/java/com/example/exampleandroidapp/HTTPWorker.java deleted file mode 100644 index 0a08a8d..0000000 --- a/examples/android/ExampleAndroidApp/app/src/main/java/com/example/exampleandroidapp/HTTPWorker.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.example.exampleandroidapp; - -import com.zerotier.libzt.ZeroTierSocketFactory; -import com.zerotier.libzt.ZeroTier; - -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.util.concurrent.ThreadLocalRandom; - -import okhttp3.FormBody; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -public class HTTPWorker extends Thread { - - @Override - public void run() { - long tid = Thread.currentThread().getId(); - // Test: Perform randomly-delayed HTTP GET requests - if (true) { - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - builder.socketFactory(new ZeroTierSocketFactory()); - OkHttpClient client = builder.build(); - Request request1 = new Request.Builder() - .url("http://11.7.7.223:80/warandpeace.txt") - .build(); - Request request2 = new Request.Builder() - .url("http://11.7.7.223:8082/pumpkin.jpg") - .build(); - RequestBody formBody = new FormBody.Builder() - .add("message", "Your message") - .build(); - Request request3 = new Request.Builder() - .url("http://11.7.7.223:8082/") - .post(formBody) - .build(); - - long i = 0; - for (;;) { - try { - int randomNum = ThreadLocalRandom.current().nextInt(0, 2 + 1); - i++; - Response response = null; - if (randomNum == 0) { - response = client.newCall(request1).execute(); - } - if (randomNum == 1) { - //response = client.newCall(request2).execute(); - response = client.newCall(request1).execute(); - } - if (randomNum == 2) { - //response = client.newCall(request3).execute(); - response = client.newCall(request1).execute(); - //System.out.println(tid+"::POST"); - //continue; - } - InputStream in = response.body().byteStream(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - int nRead; - byte[] data = new byte[16384]; - while ((nRead = in.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - } - System.out.println(tid+"::GET: i="+i+", len="+buffer.toByteArray().length); - - } catch (Exception e) { - System.out.println(e); - e.printStackTrace(); - } - - } - } - } - -} \ No newline at end of file diff --git a/examples/android/ExampleAndroidApp/app/src/main/java/com/example/exampleandroidapp/MainActivity.java b/examples/android/ExampleAndroidApp/app/src/main/java/com/example/exampleandroidapp/MainActivity.java deleted file mode 100644 index 38b879a..0000000 --- a/examples/android/ExampleAndroidApp/app/src/main/java/com/example/exampleandroidapp/MainActivity.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.example.exampleandroidapp; - -// OS imports -import android.support.v7.app.AppCompatActivity; -import android.os.Bundle; -import android.os.Environment; -import android.Manifest; - -// Misc imports -import java.util.ArrayList; -import java.util.List; - -// ZeroTier imports -import com.zerotier.libzt.ZeroTier; -import com.zerotier.libzt.ZeroTierSocket; -import com.zerotier.libzt.ZeroTierSocketFactory; -import com.zerotier.libzt.ZeroTierSSLSocketFactory; -import com.zerotier.libzt.ZeroTierSocketAddress; -import com.zerotier.libzt.ZeroTierSocketOptionValue; -import com.zerotier.libzt.ZeroTierSocketImplFactory; -import com.zerotier.libzt.ZeroTierProtoStats; - -// Custom ZeroTierEventListener -import com.example.exampleandroidapp.MyZeroTierEventListener; - -public class MainActivity extends AppCompatActivity { - - static void sleep(int ms) - { - try { Thread.sleep(ms); } - catch (InterruptedException e) { e.printStackTrace(); } - } - - void tests() - { - // Start ZeroTier service and wait for it to come online - System.out.println("Starting ZeroTier..."); - MyZeroTierEventListener listener = new MyZeroTierEventListener(); - ZeroTier.start(getApplicationContext().getFilesDir() + "/zerotier3", listener, 9994); - while (listener.isOnline == false) { sleep (50); } - System.out.println("joining network..."); - ZeroTier.join(0xa09acf0233e4b070L); - System.out.println("waiting for callback"); - while (listener.isNetworkReady == false) { sleep (50); } - - boolean testBackgroundWorkerGET = true; - boolean testRestart = true; - boolean testProtocolStats = true; - - if (testRestart) { - for (int i=0; i<10; i++) { - System.out.println("restarting..."); - ZeroTier.restart(); - sleep(10000); - } - } - - if (testProtocolStats) { - ZeroTierProtoStats protocolSpecificStats = new ZeroTierProtoStats(); - int numPings = 0; - System.out.println("recording stats..."); - while (true) { - sleep(50); - ZeroTier.get_protocol_stats(ZeroTier.STATS_PROTOCOL_ICMP, protocolSpecificStats); - if (protocolSpecificStats.recv > numPings) { - numPings = protocolSpecificStats.recv; - System.out.println("icmp.recv="+numPings); - } - } - } - - if (testBackgroundWorkerGET) { - // Start worker threads (staggered by) - List threads = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - sleep(500); - HTTPWorker thread = new HTTPWorker(); - thread.start(); - threads.add(thread); - } - try { - Thread.sleep(60000000); - } catch (Exception e) { - } - System.exit(0); - } - } - - // Entry point - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - tests(); - } -} \ No newline at end of file diff --git a/examples/android/ExampleAndroidApp/app/src/main/java/com/example/exampleandroidapp/MyZeroTierEventListener.java b/examples/android/ExampleAndroidApp/app/src/main/java/com/example/exampleandroidapp/MyZeroTierEventListener.java deleted file mode 100644 index d0e451d..0000000 --- a/examples/android/ExampleAndroidApp/app/src/main/java/com/example/exampleandroidapp/MyZeroTierEventListener.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.example.exampleandroidapp; - -import com.zerotier.libzt.ZeroTier; -import com.zerotier.libzt.ZeroTierEventListener; - -public class MyZeroTierEventListener implements ZeroTierEventListener { - - public boolean isNetworkReady = false; - public boolean isOnline = false; - - public void onZeroTierEvent(long id, int eventCode) - { - if (eventCode == ZeroTier.EVENT_NODE_UP) { - // Safe to ignore this callback - //System.out.println("EVENT_NODE_UP"); - } - if (eventCode == ZeroTier.EVENT_NODE_ONLINE) { - // The core service is running properly and can join networks now - System.out.println("EVENT_NODE_ONLINE: nodeId=" + Long.toHexString(id)); - isOnline = true; - } - if (eventCode == ZeroTier.EVENT_NODE_OFFLINE) { - // Network does not seem to be reachable by any available strategy - System.out.println("EVENT_NODE_OFFLINE"); - } - if (eventCode == ZeroTier.EVENT_NODE_DOWN) { - // Called when the node is shutting down - System.out.println("EVENT_NODE_DOWN"); - } - if (eventCode == ZeroTier.EVENT_NODE_IDENTITY_COLLISION) { - // Another node with this identity already exists - System.out.println("EVENT_NODE_IDENTITY_COLLISION"); - } - if (eventCode == ZeroTier.EVENT_NODE_UNRECOVERABLE_ERROR) { - // Try again - System.out.println("EVENT_NODE_UNRECOVERABLE_ERROR"); - } - if (eventCode == ZeroTier.EVENT_NODE_NORMAL_TERMINATION) { - // Normal closure - System.out.println("EVENT_NODE_NORMAL_TERMINATION"); - } - if (eventCode == ZeroTier.EVENT_NETWORK_READY_IP4) { - // We have at least one assigned address and we've received a network configuration - System.out.println("ZTS_EVENT_NETWORK_READY_IP4: nwid=" + Long.toHexString(id)); - isNetworkReady = true; - } - if (eventCode == ZeroTier.EVENT_NETWORK_READY_IP6) { - // We have at least one assigned address and we've received a network configuration - System.out.println("ZTS_EVENT_NETWORK_READY_IP6: nwid=" + Long.toHexString(id)); - isNetworkReady = true; - } - if (eventCode == ZeroTier.EVENT_NETWORK_DOWN) { - // Someone called leave(), we have no assigned addresses, or otherwise cannot use this interface - System.out.println("EVENT_NETWORK_DOWN: nwid=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_NETWORK_REQUESTING_CONFIG) { - // Waiting for network configuration - System.out.println("EVENT_NETWORK_REQUESTING_CONFIG: nwid=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_NETWORK_OK) { - // Config received and this node is authorized for this network - System.out.println("EVENT_NETWORK_OK: nwid=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_NETWORK_ACCESS_DENIED) { - // You are not authorized to join this network - System.out.println("EVENT_NETWORK_ACCESS_DENIED: nwid=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_NETWORK_NOT_FOUND) { - // The virtual network does not exist - System.out.println("EVENT_NETWORK_NOT_FOUND: nwid=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_NETWORK_CLIENT_TOO_OLD) { - // The core version is too old - System.out.println("EVENT_NETWORK_CLIENT_TOO_OLD: nwid=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_PEER_P2P) { - System.out.println("EVENT_PEER_P2P: id=" + Long.toHexString(id)); - } - if (eventCode == ZeroTier.EVENT_PEER_RELAY) { - System.out.println("EVENT_PEER_RELAY: id=" + Long.toHexString(id)); - } - } -} diff --git a/examples/android/ExampleAndroidApp/app/src/test/java/com/example/exampleandroidapp/ExampleUnitTest.java b/examples/android/ExampleAndroidApp/app/src/test/java/com/example/exampleandroidapp/ExampleUnitTest.java deleted file mode 100644 index d5700a9..0000000 --- a/examples/android/ExampleAndroidApp/app/src/test/java/com/example/exampleandroidapp/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.exampleandroidapp; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/examples/android/ExampleAndroidApp/build.gradle b/examples/android/ExampleAndroidApp/build.gradle deleted file mode 100644 index 43c0708..0000000 --- a/examples/android/ExampleAndroidApp/build.gradle +++ /dev/null @@ -1,27 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - - repositories { - google() - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:3.1.3' - - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -allprojects { - repositories { - google() - jcenter() - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/examples/android/ExampleAndroidApp/gradle.properties b/examples/android/ExampleAndroidApp/gradle.properties deleted file mode 100644 index 743d692..0000000 --- a/examples/android/ExampleAndroidApp/gradle.properties +++ /dev/null @@ -1,13 +0,0 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true diff --git a/examples/android/ExampleAndroidApp/gradle/wrapper/gradle-wrapper.properties b/examples/android/ExampleAndroidApp/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index d1dd0fc..0000000 --- a/examples/android/ExampleAndroidApp/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Tue Jul 31 12:01:02 PDT 2018 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/examples/android/ExampleAndroidApp/gradlew b/examples/android/ExampleAndroidApp/gradlew deleted file mode 100755 index cccdd3d..0000000 --- a/examples/android/ExampleAndroidApp/gradlew +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env sh - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/examples/android/ExampleAndroidApp/gradlew.bat b/examples/android/ExampleAndroidApp/gradlew.bat deleted file mode 100644 index e95643d..0000000 --- a/examples/android/ExampleAndroidApp/gradlew.bat +++ /dev/null @@ -1,84 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/examples/android/ExampleAndroidApp/settings.gradle b/examples/android/ExampleAndroidApp/settings.gradle deleted file mode 100644 index e7b4def..0000000 --- a/examples/android/ExampleAndroidApp/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':app' From dd6cf48d611471745b3fc5b9139c0a0dda0f5a02 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Sun, 7 Mar 2021 21:11:21 -0800 Subject: [PATCH 23/25] Run Python language bindings and example code through a linter and formatter --- examples/python/example.py | 276 +++++++------ src/bindings/python/sockets.py | 717 ++++++++++++++++++--------------- 2 files changed, 526 insertions(+), 467 deletions(-) diff --git a/examples/python/example.py b/examples/python/example.py index e00ab35..ccecf3d 100644 --- a/examples/python/example.py +++ b/examples/python/example.py @@ -1,159 +1,157 @@ -import time, sys +'''Example low-level socket usage''' + +import time +import sys import libzt -# 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: \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) + '''print help''' + print( + "\nUsage: \n" + ) + print("Ex: python3 demo.py server . 0123456789abcdef 9994 8080") + print("Ex: python3 demo.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") + sys.exit(0) +is_joined = False # Flags to keep state +is_online = False # Flags to keep state # # Event handler # class MyEventCallbackClass(libzt.EventCallbackClass): - 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") - - + 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 +# Main # -print("Starting ZeroTier..."); -eventCallback = MyEventCallbackClass() -libzt.start(keyPath, eventCallback, ztServicePort) -print("Waiting for node to come online...") -while (not is_online): - time.sleep(1) -print("Joining network:", hex(networkId)); -libzt.join(networkId) -while (not is_joined): - time.sleep(1) # You can ping this app at this point -print('Joined network') +def main(): + global is_online + global is_joined + key_file_path = "." # Where identity files are stored + network_id = 0 # Network to join + # Port used by ZeroTier to send encpryted UDP traffic + # NOTE: Should be different from other instances of ZeroTier + # running on the same machine + zt_service_port = 9997 + remote_ip = None # ZeroTier IP of remote node + remote_port = 8080 # ZeroTier port your app logic may use + mode = None # client|server + 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] + key_file_path = sys.argv[2] + network_id = int(sys.argv[3], 16) + zt_service_port = int(sys.argv[4]) + remote_port = int(sys.argv[5]) + if sys.argv[1] == "client" and len(sys.argv) == 7: + mode = sys.argv[1] + key_file_path = sys.argv[2] + network_id = int(sys.argv[3], 16) + zt_service_port = int(sys.argv[4]) + remote_ip = sys.argv[5] + remote_port = int(sys.argv[6]) + if mode is None: + print_usage() + print("mode = ", mode) + print("path = ", key_file_path) + print("network_id = ", network_id) + print("zt_service_port = ", zt_service_port) + print("remote_ip = ", remote_ip) + print("remote_port = ", remote_port) -# -# Example server -# -if (mode == 'server'): - print("Starting server...") - serv = libzt.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) - try: - #serv.setblocking(True) - 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) - print('errno=',libzt.errno()) # See include/ZeroTierSockets.h for codes + # + # Example start and join logic + # + print("Starting ZeroTier...") + event_callback = MyEventCallbackClass() + libzt.start(key_file_path, event_callback, zt_service_port) + print("Waiting for node to come online...") + while not is_online: + time.sleep(1) + print("Joining network:", hex(network_id)) + libzt.join(network_id) + 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...") + serv = libzt.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) + try: + # serv.setblocking(True) + serv.bind(("0.0.0.0", remote_port)) + 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 ex: + print(ex) + print("errno=", libzt.errno()) # See include/ZeroTierSockets.h for codes -# -# Example client -# -if (mode == 'client'): - print("Starting client...") - client = libzt.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) - try: - print("connecting...") - client.connect((remoteIP, serverPort)) - print("send:") - data = 'Hello, world!' - client.send(data) - data = client.recv(1024) - print('Received', repr(data)) - except Exception as e: - print(e) - print('errno=',libzt.errno()) + # + # Example client + # + if mode == "client": + print("Starting client...") + client = libzt.socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) + try: + print("connecting...") + client.connect((remote_ip, remote_port)) + print("send:") + data = "Hello, world!" + client.send(data) + data = client.recv(1024) + print("Received", repr(data)) + except Exception as ex: + print(ex) + print("errno=", libzt.errno()) +if __name__ == "__main__": + main() diff --git a/src/bindings/python/sockets.py b/src/bindings/python/sockets.py index ac510fd..e8d3dc3 100644 --- a/src/bindings/python/sockets.py +++ b/src/bindings/python/sockets.py @@ -1,347 +1,408 @@ +"""ZeroTier low-level socket interface""" + import libzt class EventCallbackClass(libzt.PythonDirectorCallbackClass): - pass + """ZeroTier event callback class""" + pass -# 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) + ')') + """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 libzt.cvar.zts_errno + """Return errno value of low-level socket layer""" + return libzt.cvar.zts_errno -# Start the ZeroTier service def start(path, callback, port): - libzt.zts_start(path, callback, port) + """Start the ZeroTier service""" + libzt.zts_start(path, callback, port) -# Stop the ZeroTier service def stop(): - libzt.zts_stop() + """Stop the ZeroTier service""" + libzt.zts_stop() -# [debug] Restarts the ZeroTier service and network stack def restart(): - libzt.zts_restart() + """[debug] Restarts the ZeroTier service and network stack""" + libzt.zts_restart() -# Permenantly shuts down the network stack. def free(): - libzt.zts_free() - -# Join a ZeroTier network -def join(networkId): - libzt.zts_join(networkId) - -# Leave a ZeroTier network -def leave(networkId): - libzt.zts_leave(networkId) - -# Orbit a moon -def zts_orbit(moonWorldId, moonSeed): - return libzt.zts_orbit(moonWorldId, moonSeed) - -# De-orbit a moon -def zts_deorbit(moonWorldId): - return libzt.zts_deorbit(moonWorldId) - -# Pythonic class that wraps low-level sockets -class socket(): - - _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 - - @property - def proto(self): - return _proto - - # Intentionally not supported - def socketpair(self, family, type, proto): - raise NotImplementedError("socketpair(): libzt does not support AF_UNIX sockets") - - # Convenience function to create a connection to a remote host - def create_connection(self, remote_address): - # TODO: implement timeout - conn = socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) - conn.connect(remote_address) - return conn - - # Convenience function to create a listening socket - def create_server(self, local_address, family=libzt.ZTS_AF_INET, backlog=None): - # TODO: implement reuse_port - conn = socket(libzt.ZTS_AF_INET, libzt.ZTS_SOCK_STREAM, 0) - conn.bind(local_address) - conn.listen(backlog) - return conn - - def fromfd(self, fd, family, type, proto=0): - raise NotImplementedError("fromfd(): Not supported. OS File descriptors aren't used in libzt.") - - def fromshare(self, data): - raise NotImplementedError("libzt does not support this (yet?)") - - def close(self, fd): - raise NotImplementedError("close(fd): Not supported OS File descriptors aren't used in libzt.") - - def getaddrinfo(self, host, port, family=0, type=0, proto=0, flags=0): - raise NotImplementedError("libzt does not support this (yet?)") - - def getfqdn(self, name): - raise NotImplementedError("libzt does not support this (yet?)") - - def gethostbyname(self, hostname): - raise NotImplementedError("libzt does not support this (yet?)") - - def gethostbyname_ex(self, hostname): - raise NotImplementedError("libzt does not support this (yet?)") - - def gethostname(self): - raise NotImplementedError("libzt does not support this (yet?)") - - def gethostbyaddr(self, ip_address): - raise NotImplementedError("libzt does not support this (yet?)") - - def getnameinfo(self, sockaddr, flags): - raise NotImplementedError("libzt does not support this (yet?)") - - def getprotobyname(self, protocolname): - raise NotImplementedError("libzt does not support this (yet?)") - - def getservbyname(self, servicename, protocolname): - raise NotImplementedError("libzt does not support this (yet?)") - - def getservbyport(self, port, protocolname): - raise NotImplementedError("libzt does not support this (yet?)") - - def ntohl(x): - raise NotImplementedError("libzt does not support this (yet?)") - - def ntohs(x): - raise NotImplementedError("libzt does not support this (yet?)") - - def htonl(x): - raise NotImplementedError("libzt does not support this (yet?)") - - def htons(x): - raise NotImplementedError("libzt does not support this (yet?)") - - def inet_aton(ip_string): - raise NotImplementedError("libzt does not support this (yet?)") - - def inet_ntoa(packed_ip): - raise NotImplementedError("libzt does not support this (yet?)") - - def inet_pton(address_family, ip_string): - raise NotImplementedError("libzt does not support this (yet?)") - - def inet_ntop(address_family, packed_ip): - raise NotImplementedError("libzt does not support this (yet?)") - - def CMSG_LEN(length): - raise NotImplementedError("libzt does not support this (yet?)") - - def CMSG_SPACE(length): - raise NotImplementedError("libzt does not support this (yet?)") - - def getdefaulttimeout(self): - raise NotImplementedError("libzt does not support this (yet?)") - - def setdefaulttimeout(self, timeout): - raise NotImplementedError("libzt does not support this (yet?)") - - def sethostname(self, name): - raise NotImplementedError("libzt does not support this (yet?)") - - def if_nameindex(self): - raise NotImplementedError("libzt does not support this (yet?)") - - def if_nametoindex(self, if_name): - raise NotImplementedError("if_nametoindex(): libzt does not name interfaces.") - - def if_indextoname(self, if_index): - raise NotImplementedError("if_indextoname(): libzt does not name interfaces.") - - # Accept connection on the socket - def accept(self): - new_conn_fd, addr, port = libzt.zts_py_accept(self._fd) - if (new_conn_fd < 0): - handle_error(new_conn_fd) - return None - return ztsocket(self._family, self._type, self._proto, new_conn_fd), addr - - # 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): - handle_error(err) - - # Close the socket - def close(self): - err = libzt.zts_py_close(self._fd) - if (err < 0): - 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): - handle_error(err) - - # Connect to remote host but return low-level result code, and errno on failure - # This uses a non-thread-safe implementation of errno - def connect_ex(self, remote_address): - err = libzt.zts_py_connect(self._fd, self._family, self._type, remote_address) - if (err < 0): - return errno() - return err - - def detach(self): - raise NotImplementedError("detach(): Not supported. OS File descriptors aren't used in libzt.") - - def dup(self): - raise NotImplementedError("libzt does not support this (yet?)") - - def fileno(self): - raise NotImplementedError("libzt does not support this (yet?)") - - def get_inheritable(self): - raise NotImplementedError("libzt does not support this (yet?)") - - def getpeername(self): - raise NotImplementedError("libzt does not support this (yet?)") - - def getsockname(self): - raise NotImplementedError("libzt does not support this (yet?)") - - def getsockopt(self, level, optname, buflen): - raise NotImplementedError("libzt does not support this (yet?)") - - # Get whether this socket is in blocking or non-blocking mode - def getblocking(self): - return libzt.zts_py_getblocking(self._fd) - - def gettimeout(self): - raise NotImplementedError("libzt does not support this (yet?)") - - def ioctl(self, control, option): - raise NotImplementedError("libzt does not support this (yet?)") - - # 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): - handle_error(err) - - def makefile(mode='r', buffering=None, *, encoding=None, errors=None, newline=None): - raise NotImplementedError("libzt does not support this (yet?)") - - # 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): - handle_error(err) - return None - return data - - def recvfrom(self, bufsize, flags): - raise NotImplementedError("libzt does not support this (yet?)") - - def recvmsg(self, bufsize, ancbufsize, flags): - raise NotImplementedError("libzt does not support this (yet?)") - - def recvmsg_into(self, buffers, ancbufsize, flags): - raise NotImplementedError("libzt does not support this (yet?)") - - def recvfrom_into(self, buffer, nbytes, flags): - raise NotImplementedError("libzt does not support this (yet?)") - - def recv_into(self, buffer, nbytes, flags): - raise NotImplementedError("libzt does not support this (yet?)") - - # 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): - handle_error(err) - return err - - def sendall(self, bytes, flags): - raise NotImplementedError("libzt does not support this (yet?)") - - def sendto(self, bytes, address): - raise NotImplementedError("libzt does not support this (yet?)") - - def sendto(self, bytes, flags, address): - raise NotImplementedError("libzt does not support this (yet?)") - - def sendmsg(self, buffers, ancdata, flags, address): - raise NotImplementedError("libzt does not support this (yet?)") - - def sendmsg_afalg(self, msg, *, op, iv, assoclen, flags): - raise NotImplementedError("sendmsg_afalg(): libzt does not support AF_ALG") - - def send_fds(self, sock, buffers, fds, flags, address): - raise NotImplementedError("libzt does not support this (yet?)") - - def recv_fds(self, sock, bufsize, maxfds, flags): - raise NotImplementedError("libzt does not support this (yet?)") - - def sendfile(self, file, offset=0, count=None): - raise NotImplementedError("libzt does not support this (yet?)") - - def set_inheritable(self, inheritable): - raise NotImplementedError("libzt does not support this (yet?)") - - # Set whether this socket is in blocking or non-blocking mode - def setblocking(self, flag): - libzt.zts_py_setblocking(self._fd, flag) - - def settimeout(self, value): - raise NotImplementedError("libzt does not support this (yet?)") - - def setsockopt(self, level, optname, value): - # TODO: value: buffer - # TODO: value: int - # TODO: value: None -> optlen required - raise NotImplementedError("libzt does not support this (yet?)") - - # Shut down one or more aspects (rx/tx) of the socket - def shutdown(self, how): - libzt.shutdown(self._fd, how) + """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 connection on the socket""" + 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 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 the socket""" + err = libzt.zts_py_close(self._fd) + if err < 0: + handle_error(err) + + def connect(self, remote_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 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 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 getblocking(self): + """Get whether this socket is in blocking or non-blocking mode""" + return libzt.zts_py_getblocking(self._fd) + + 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): + """Put the socket in a listening state (with an optional backlog argument)""" + 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): + """Read data from the socket""" + 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): + """Write data to the socket""" + err = libzt.zts_py_send(self._fd, data, len(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): + """Set whether this socket is in blocking or non-blocking mode""" + 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): + """Shut down one or more aspects (rx/tx) of the socket""" + libzt.zts_shutdown(self._fd, how) From 0080bd0fdd6a01b25d3fddf6fee706492010651c Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Wed, 10 Mar 2021 11:20:00 -0800 Subject: [PATCH 24/25] Minor update to documentation --- README.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 4f00b85..f707a1c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@
+

ZeroTier SDK

+zts_socket() + Peer-to-peer and cross-platform encrypted connections built right into your app or service. No drivers, no root, and no host configuration.
@@ -7,10 +10,12 @@ Peer-to-peer and cross-platform encrypted connections built right into your app Examples | API Documentation | Community | -FAQ | Report a Bug -
+ +@zerotier +r/zerotier + latest libzt version Last Commit @@ -19,18 +24,18 @@ Peer-to-peer and cross-platform encrypted connections built right into your app | Language/Platform | Installation | Version | Example | |:----------|:---------|:---|:---| -| C/C++ | [Build from source](./BUILDING.md) | version|[C/C++](./examples/cpp) | +| C/C++ | [Build from source](#build-from-source) | version|[C/C++](./examples/cpp) | | Objective-C | See [examples/objective-c](./examples/objective-c) | version |[Objective-C](./examples/objective-c) | | C# | `Install-Package ZeroTier.Sockets` | |[C#](./examples/csharp) | | Python | `pip install libzt`| |[Python](./examples/python) | -| Rust | Coming Soon | version|[Rust](./examples/rust) | +| Rust | Coming *very* soon | version|[Rust](./examples/rust) | | Swift | See [examples/swift](./examples/swift) |version |[Swift](./examples/swift) | -| Java | See [examples/java](./examples/java) | |[Java](./examples/java) | +| Java | `./build.sh host-jar` | |[Java](./examples/java) | | Node.js | See [examples/nodejs](./examples/nodejs) ||[Node.js](./examples/nodejs) | | Linux | [Build from source](#build-from-source) | version| [C/C++](./examples/cpp) | | macOS | `brew install libzt`| | [C/C++](./examples/cpp), [Objective-C](./examples/objective-c) | -| iOS / iPadOS | [zt.framework]() | | [Objective-C](./examples/objective-c), [Swift](./examples/swift) | -| Android | [zt.aar]() | | [Java](./examples/java) | +| iOS / iPadOS | `./build.sh ios-framework` | | [Objective-C](./examples/objective-c), [Swift](./examples/swift) | +| Android |`./build.sh android-aar` | | [Java](./examples/java) |
@@ -98,10 +103,7 @@ While we do operate a global network of redundant root servers, network controll - Examples: [examples/](./examples) - Bug reports: [Open a github issue](https://github.com/zerotier/libzt/issues). - General ZeroTier troubleshooting: [Knowledgebase](https://zerotier.atlassian.net/wiki/spaces/SD/overview). - - Talk to us: - - Community: [discuss.zerotier.com](https://discuss.zerotier.com) - - @zerotier - - r/zerotier + - Chat with us: [discuss.zerotier.com](https://discuss.zerotier.com) # Licensing From 301cf9f41b6fe8f583849b4f01791ba67ad77d6b Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Fri, 12 Mar 2021 20:20:36 -0800 Subject: [PATCH 25/25] Add GitHub workflow script to build Python wheels. Update PyPI package --- .github/workflows/wheels.yml | 31 +++++ ext/THIRDPARTY.txt | 219 +++++++++++++++++++++++++++++++++++ pkg/pypi/LICENSE | 149 ++++++++++++++++++++++++ pkg/pypi/build.sh | 23 +++- pkg/pypi/libzt/__init__.py | 3 +- pkg/pypi/libzt/version.py | 1 + pkg/pypi/setup.cfg | 4 +- pkg/pypi/setup.py | 20 ++-- 8 files changed, 432 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/wheels.yml create mode 100644 ext/THIRDPARTY.txt create mode 100644 pkg/pypi/LICENSE create mode 100644 pkg/pypi/libzt/version.py diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 0000000..0bef61c --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,31 @@ +name: Build New Wheels +on: [workflow_dispatch] +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macOS-latest] + + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + + - uses: actions/setup-python@v2 + + - name: Install cibuildwheel + run: python -m pip install cibuildwheel==1.10.0 + + - name: Build wheels + env: + CIBW_ARCHS: auto + CIBW_BUILD: cp35-* cp36-* cp37-* cp38-* cp39-* + CIBW_BEFORE_BUILD: ln -s $(pwd) pkg/pypi/native; cp -f src/bindings/python/*.py pkg/pypi/libzt/; cd pkg/pypi; python setup.py build_clib + run: | + python -m cibuildwheel pkg/pypi --output-dir wheelhouse + + - uses: actions/upload-artifact@v2 + with: + path: wheelhouse/*.whl diff --git a/ext/THIRDPARTY.txt b/ext/THIRDPARTY.txt new file mode 100644 index 0000000..fd1300c --- /dev/null +++ b/ext/THIRDPARTY.txt @@ -0,0 +1,219 @@ +Third-Party Code + +ZeroTier and the ZeroTier SDK (including libzt) includes the following third +party code, either in ext/ or incorporated into the ZeroTier core. This third +party code remains licensed under its original license and is not subject to +ZeroTier's BSL license. + + + + + + + +LZ4 compression algorithm by Yann Collet - http://code.google.com/p/lz4/ +----------------------------------------------------------------------------- + +LZ4 - Fast LZ compression algorithm +Header File +Copyright (C) 2011-2016, Yann Collet. + +BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +You can contact the author at : +- LZ4 homepage : http://www.lz4.org +- LZ4 source repository : https://github.com/lz4/lz4 + + + + + + + +C++11 json (nlohmann/json) by Niels Lohmann +https://github.com/nlohmann/json +----------------------------------------------------------------------------- + +MIT License + +Copyright (c) 2013-2021 Niels Lohmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + + + + + +MiniUPNPC and libnatpmp by Thomas Bernard - http://miniupnp.free.fr/ +----------------------------------------------------------------------------- + +MiniUPnPc +Copyright (c) 2005-2016, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + + + + + + +lwIP - https://www.nongnu.org/lwip/2_1_x/index.html +----------------------------------------------------------------------------- + +Copyright (c) 2001-2003 Swedish Institute of Computer Science. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. + +This file is part of the lwIP TCP/IP stack. + +Author: Adam Dunkels + + + + + + + +concurrentqueue - https://github.com/cameron314/concurrentqueue +----------------------------------------------------------------------------- + +Simplified BSD License: + +Copyright (c) 2013-2016, Cameron Desrochers. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, this list of +conditions and the following disclaimer in the documentation and/or other materials +provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + + + + +curl - https://curl.se/ +----------------------------------------------------------------------------- + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1996 - 2021, Daniel Stenberg, , and many +contributors, see the THANKS file. + +All rights reserved. + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright +notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall not +be used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization of the copyright holder. diff --git a/pkg/pypi/LICENSE b/pkg/pypi/LICENSE new file mode 100644 index 0000000..9784015 --- /dev/null +++ b/pkg/pypi/LICENSE @@ -0,0 +1,149 @@ +----------------------------------------------------------------------------- + +Business Source License 1.1 + +License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. +"Business Source License" is a trademark of MariaDB Corporation Ab. + +----------------------------------------------------------------------------- + +Parameters + +Licensor: ZeroTier, Inc. +Licensed Work: ZeroTier Network Virtualization Engine 1.4.4 + The Licensed Work is (c)2019 ZeroTier, Inc. +Additional Use Grant: You may make use of the Licensed Work, provided you + do not use it in any of the following ways: + + * Sell hosted ZeroTier services as a "SaaS" Product + + (1) Operate or sell access to ZeroTier root servers, + network controllers, or authorization key or certificate + generation components of the Licensed Work as a + for-profit service, regardless of whether the use of + these components is sold alone or is bundled with other + services. Note that this does not apply to the use of + ZeroTier behind the scenes to operate a service not + related to ZeroTier network administration. + + * Create Non-Open-Source Commercial Derviative Works + + (2) Link or directly include the Licensed Work in a + commercial or for-profit application or other product + not distributed under an Open Source Initiative (OSI) + compliant license. See: https://opensource.org/licenses + + (3) Remove the name, logo, copyright, or other branding + material from the Licensed Work to create a "rebranded" + or "white labeled" version to distribute as part of + any commercial or for-profit product or service. + + * Certain Government Uses + + (4) Use or deploy the Licensed Work in a government + setting in support of any active government function + or operation with the exception of the following: + physical or mental health care, family and social + services, social welfare, senior care, child care, and + the care of persons with disabilities. + +Change Date: 2023-01-01 + +Change License: Apache License version 2.0 as published by the Apache + Software Foundation + https://www.apache.org/licenses/ + +Alternative Licensing + +If you would like to use the Licensed Work in any way that conflicts with +the stipulations of the Additional Use Grant, contact ZeroTier, Inc. to +obtain an alternative commercial license. + +Visit us on the web at: https://www.zerotier.com/ + +Notice + +The Business Source License (this document, or the "License") is not an Open +Source license. However, the Licensed Work will eventually be made available +under an Open Source License, as stated in this License. + +For more information on the use of the Business Source License for ZeroTier +products, please visit our pricing page which contains license details and +and license FAQ: https://zerotier.com/pricing + +For more information on the use of the Business Source License generally, +please visit the Adopting and Developing Business Source License FAQ at +https://mariadb.com/bsl-faq-adopting. + +----------------------------------------------------------------------------- + +Business Source License 1.1 + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative +works, redistribute, and make non-production use of the Licensed Work. The +Licensor may make an Additional Use Grant, above, permitting limited +production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly +available distribution of a specific version of the Licensed Work under this +License, whichever comes first, the Licensor hereby grants you rights under +the terms of the Change License, and the rights granted in the paragraph +above terminate. + +If your use of the Licensed Work does not comply with the requirements +currently in effect as described in this License, you must purchase a +commercial license from the Licensor, its affiliated entities, or authorized +resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works +of the Licensed Work, are subject to this License. This License applies +separately for each version of the Licensed Work and the Change Date may vary +for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy +of the Licensed Work. If you receive the Licensed Work in original or +modified form from a third party, the terms and conditions set forth in this +License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically +terminate your rights under this License for the current and all other +versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of +Licensor or its affiliates (provided that you may use a trademark or logo of +Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +TITLE. + +----------------------------------------------------------------------------- + +MariaDB hereby grants you permission to use this License’s text to license +your works, and to refer to it using the trademark "Business Source License", +as long as you comply with the Covenants of Licensor below. + +Covenants of Licensor + +In consideration of the right to use this License’s text and the "Business +Source License" name and trademark, Licensor covenants to MariaDB, and to all +other recipients of the licensed work to be provided by Licensor: + +1. To specify as the Change License the GPL Version 2.0 or any later version, + or a license that is compatible with GPL Version 2.0 or a later version, + where "compatible" means that software provided under the Change License can + be included in a program with software provided under GPL Version 2.0 or a + later version. Licensor may specify additional Change Licenses without + limitation. + +2. To either: (a) specify an additional grant of rights to use that does not + impose any additional restriction on the right granted in this License, as + the Additional Use Grant; or (b) insert the text "None". + +3. To specify a Change Date. + +4. Not to modify this License in any other way. \ No newline at end of file diff --git a/pkg/pypi/build.sh b/pkg/pypi/build.sh index df8527e..cdf5fb4 100755 --- a/pkg/pypi/build.sh +++ b/pkg/pypi/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -PYBIN=python3 +PYBIN=python3.9 #PYBIN=/opt/python/cp39-cp39/bin/python3 # Build the extension module @@ -29,7 +29,7 @@ clean() find . -name '*.so' -type f -delete find . -name '*.pyc' -type f -delete find . -name '__pycache__' -type d -delete - rm -rf libzt/prototype.py + rm -rf libzt/sockets.py rm -rf libzt/libzt.py rm -rf src ext build dist native rm -rf libzt.egg-info @@ -38,9 +38,24 @@ clean() manylinux() { - CONTAINER="quay.io/pypa/manylinux_2_24_aarch64" + CONTAINER="quay.io/pypa/manylinux_2_24_x86_64" docker pull ${CONTAINER} docker run --rm -it --entrypoint bash -v $(pwd)/../../:/media/libzt ${CONTAINER} } -"$@" \ No newline at end of file +cycle() +{ + #clean + #swig -c++ -python -o ../../src/bindings/python/zt_wrap.cpp -I../../include ../../src/bindings/python/zt.i + #wheel + #pip3 uninstall -y libzt + #pip3 install dist/libzt-1.3.3-cp39-cp39-macosx_11_0_x86_64.whl + +} + +update-version() +{ + echo "__version__ = \"$(git describe)\"" > libzt/version.py +} + +"$@" diff --git a/pkg/pypi/libzt/__init__.py b/pkg/pypi/libzt/__init__.py index 9c0487c..8e5a8ac 100644 --- a/pkg/pypi/libzt/__init__.py +++ b/pkg/pypi/libzt/__init__.py @@ -1,4 +1,3 @@ -__version__ = "1.3.3" - from .libzt import * from .sockets import * +from .version import __version__ diff --git a/pkg/pypi/libzt/version.py b/pkg/pypi/libzt/version.py new file mode 100644 index 0000000..9633af7 --- /dev/null +++ b/pkg/pypi/libzt/version.py @@ -0,0 +1 @@ +__version__ = "1.3.4b0" diff --git a/pkg/pypi/setup.cfg b/pkg/pypi/setup.cfg index 36b1ca0..239590c 100644 --- a/pkg/pypi/setup.cfg +++ b/pkg/pypi/setup.cfg @@ -1,4 +1,6 @@ [metadata] version = attr: libzt.__version__ description-file = README.md -license_files = LICENSE \ No newline at end of file +license_files = + native/LICENSE.txt + native/ext/THIRDPARTY.txt diff --git a/pkg/pypi/setup.py b/pkg/pypi/setup.py index 2af77a6..9597092 100644 --- a/pkg/pypi/setup.py +++ b/pkg/pypi/setup.py @@ -1,9 +1,15 @@ #!/usr/bin/env python from setuptools import setup, Extension, Command, Distribution +from distutils.util import convert_path import glob import os +main_ns = {} +ver_path = convert_path('libzt/version.py') +with open(ver_path) as ver_file: + exec(ver_file.read(), main_ns) + from os import path this_directory = path.abspath(path.dirname(__file__)) with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: @@ -70,9 +76,8 @@ cstuff = ('cstuff', {'sources': setup( name = 'libzt', - version = '1.3.3', + version = main_ns['__version__'], description = 'ZeroTier', -# long_description = 'Encrypted P2P communication between apps and services', long_description=long_description, long_description_content_type='text/markdown', author = 'ZeroTier, Inc.', @@ -87,28 +92,21 @@ setup( 'Topic :: Internet', 'Topic :: System :: Networking', 'Topic :: Security :: Cryptography', - 'Operating System :: OS Independent', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', - 'Intended Audience :: Science/Research', 'Intended Audience :: System Administrators', 'Intended Audience :: Telecommunications Industry', 'Intended Audience :: End Users/Desktop', 'License :: Free for non-commercial use', 'Operating System :: MacOS', - 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: BSD', 'Operating System :: POSIX :: Linux', 'Operating System :: Unix', 'Programming Language :: C', 'Programming Language :: C++', - 'Programming Language :: Python', - 'Programming Language :: Java', - 'Programming Language :: C#', - 'Programming Language :: Rust' + 'Programming Language :: Python' ], distclass=BinaryDistribution, libraries=[cstuff], ext_modules = [libzt_module], - python_requires='>=3.0', -) \ No newline at end of file +)