diff --git a/src/java/ZeroTier.java b/src/java/ZeroTier.java new file mode 100644 index 0000000..cf10a88 --- /dev/null +++ b/src/java/ZeroTier.java @@ -0,0 +1,241 @@ +/* + * ZeroTier SDK - Network Virtualization Everywhere + * Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * -- + * + * 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; + +import java.net.*; + +public class ZeroTier +{ + ////////////////////////////////////////////////////////////////////////////// + // Control API error codes // + ////////////////////////////////////////////////////////////////////////////// + + // Everything is ok + public static int ZTS_ERR_OK = 0; + // A argument provided by the user application is invalid (e.g. out of range, NULL, etc) + public static int ZTS_ERR_INVALID_ARG = -1; + // The service isn't initialized or is for some reason currently unavailable. Try again. + public static int ZTS_ERR_SERVICE = -2; + // For some reason this API operation is not permitted or doesn't make sense at this time. + public static int ZTS_ERR_INVALID_OP = -3; + // The call succeeded, but no object or relevant result was available + public static int ZTS_ERR_NO_RESULT = -4; + // General internal failure + public static int ZTS_ERR_GENERAL = -5; + + ////////////////////////////////////////////////////////////////////////////// + // 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; + + ////////////////////////////////////////////////////////////////////////////// + // ZeroTier Service Controls // + ////////////////////////////////////////////////////////////////////////////// + + public static native int start(String path, ZeroTierEventListener callbackClass, int port); + public static native int stop(); + 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 new file mode 100644 index 0000000..fd5e54e --- /dev/null +++ b/src/java/ZeroTierEventListener.java @@ -0,0 +1,9 @@ +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 new file mode 100644 index 0000000..000902b --- /dev/null +++ b/src/java/ZeroTierFileDescriptorSet.java @@ -0,0 +1,28 @@ +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 new file mode 100644 index 0000000..7a02d3a --- /dev/null +++ b/src/java/ZeroTierInputStream.java @@ -0,0 +1,209 @@ +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 new file mode 100644 index 0000000..54c6131 --- /dev/null +++ b/src/java/ZeroTierIoctlArg.java @@ -0,0 +1,6 @@ +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 new file mode 100644 index 0000000..dd39a8a --- /dev/null +++ b/src/java/ZeroTierOutputStream.java @@ -0,0 +1,81 @@ +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 new file mode 100644 index 0000000..49e8383 --- /dev/null +++ b/src/java/ZeroTierPeerDetails.java @@ -0,0 +1,47 @@ +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/ZeroTierSSLSocketFactory.java b/src/java/ZeroTierSSLSocketFactory.java new file mode 100644 index 0000000..2930f7a --- /dev/null +++ b/src/java/ZeroTierSSLSocketFactory.java @@ -0,0 +1,101 @@ +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 new file mode 100644 index 0000000..4ccebb5 --- /dev/null +++ b/src/java/ZeroTierSocket.java @@ -0,0 +1,748 @@ +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 new file mode 100644 index 0000000..e773c09 --- /dev/null +++ b/src/java/ZeroTierSocketAddress.java @@ -0,0 +1,66 @@ +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 new file mode 100644 index 0000000..0e79a65 --- /dev/null +++ b/src/java/ZeroTierSocketFactory.java @@ -0,0 +1,51 @@ +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 new file mode 100644 index 0000000..cbb21d7 --- /dev/null +++ b/src/java/ZeroTierSocketImpl.java @@ -0,0 +1,771 @@ +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 new file mode 100644 index 0000000..f04bf95 --- /dev/null +++ b/src/java/ZeroTierSocketImplFactory.java @@ -0,0 +1,29 @@ +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 new file mode 100644 index 0000000..15bbfef --- /dev/null +++ b/src/java/ZeroTierSocketOptionValue.java @@ -0,0 +1,14 @@ +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