/* * 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: 2026-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.sdk; import com.zerotier.sdk.*; import java.io.*; import java.net.*; /** * Implements Socket-like behavior over ZeroTier * * @author ZeroTier, Inc. */ public class ZeroTierSocket { // File descriptor from lower native layer private int _zfd = -1; private int _family = -1; private int _type = -1; private int _protocol = -1; // State flags private boolean _isClosed = false; private boolean _isConnected = false; private boolean _isBound = false; private boolean _inputHasBeenShutdown = false; private boolean _outputHasBeenShutdown = false; // Input and Output streams private ZeroTierInputStream _inputStream = new ZeroTierInputStream(); private ZeroTierOutputStream _outputStream = new ZeroTierOutputStream(); // The remote address to which the ZeroTierSocket is connected private InetAddress _remoteAddr; private int _remotePort; private InetAddress _localAddr; private int _localPort; private void setNativeFileDescriptor(int fd) { _zfd = fd; _inputStream.zfd = fd; _outputStream.zfd = fd; } private ZeroTierSocket(int family, int type, int protocol, int zfd) { _family = family; _type = type; _protocol = protocol; setNativeFileDescriptor(zfd); // Since we only call this from accept() we will mark it as connected _isConnected = true; } /** * Create a new ZeroTierSocket with the given attributes * @param family The socket family * @param type The socket type * @param protocol Supported protocol * * @exception IOException when an I/O error occurs */ public ZeroTierSocket(int family, int type, int protocol) throws IOException { if (_zfd > -1) { throw new IOException("This socket has already been created (fd=" + _zfd + ")"); } _zfd = ZeroTierNative.zts_bsd_socket(ZeroTierNative.ZTS_AF_INET, ZeroTierNative.ZTS_SOCK_STREAM, protocol); if (_zfd < 0) { throw new IOException("Error while creating socket (" + _zfd + ")"); } _family = family; _type = type; _protocol = protocol; setNativeFileDescriptor(_zfd); } /** * Connect to a remote host * @param remoteAddr Remote address to which this socket should connect * @param remotePort Remote port to which this socket should connect * * @exception IOException when an I/O error occurs */ public void connect(InetAddress remoteAddr, int remotePort) throws IOException { if (_zfd < 0) { throw new IOException("Invalid socket (fd < 0)"); } if ((remoteAddr instanceof Inet4Address) && _family != ZeroTierNative.ZTS_AF_INET) { throw new IOException("Invalid address type. Socket is of type AF_INET"); } if ((remoteAddr instanceof Inet6Address) && _family != ZeroTierNative.ZTS_AF_INET6) { throw new IOException("Invalid address type. Socket is of type AF_INET6"); } int err; if ((err = ZeroTierNative.zts_connect(_zfd, remoteAddr.getHostAddress(), remotePort, 0)) < 0) { throw new IOException("Error while connecting to remote host (" + err + ")"); } _isConnected = true; } /** * Connect to a remote host * @param remoteAddr Remote address to which this socket should connect * @param remotePort Remote port to which this socket should connect * * @exception IOException when an I/O error occurs */ public void connect(String remoteAddr, int remotePort) throws IOException { InetAddress remoteInetAddr = InetAddress.getByName(remoteAddr); connect(remoteInetAddr, remotePort); } /** * Connect to a remote host * @param remoteAddr Remote address to which this socket should connect * * @exception IOException when an I/O error occurs */ public void connect(SocketAddress remoteAddr) throws IOException { int remotePort = ((InetSocketAddress)remoteAddr).getPort(); connect(((InetSocketAddress)remoteAddr).getHostString(), remotePort); } /** * Bind to a local address * @param localAddr Local address to which this socket should bind * @param localPort Local port to which this socket should bind * * @exception IOException when an I/O error occurs */ public void bind(InetAddress localAddr, int localPort) throws IOException { if (_zfd < 0) { throw new IOException("Invalid socket (fd < 0)"); } if ((localAddr instanceof Inet4Address) && _family != ZeroTierNative.ZTS_AF_INET) { throw new IOException("Invalid address type. Socket is of type AF_INET"); } if ((localAddr instanceof Inet6Address) && _family != ZeroTierNative.ZTS_AF_INET6) { throw new IOException("Invalid address type. Socket is of type AF_INET6"); } int err; if ((err = ZeroTierNative.zts_bind(_zfd, localAddr.getHostAddress(), localPort)) < 0) { throw new IOException("Error while connecting to remote host (" + err + ")"); } _localPort = localPort; _isBound = true; } /** * Bind to a local address * @param localAddr Local address to which this socket should bind * @param localPort Local port to which this socket should bind * * @exception IOException when an I/O error occurs */ public void bind(String localAddr, int localPort) throws IOException { InetAddress localInetAddr = InetAddress.getByName(localAddr); bind(localInetAddr, localPort); } /** * Put the ZeroTierSocket into a listening state * @param backlog Size of connection backlog * * @exception IOException when an I/O error occurs */ public void listen(int backlog) throws IOException { if (_zfd < 0) { throw new IOException("Invalid socket (fd < 0)"); } if (backlog < 0) { throw new IOException("Invalid backlog value"); } int err; if ((err = ZeroTierNative.zts_bsd_listen(_zfd, backlog)) < 0) { throw new IOException("Error while putting socket into listening state (" + err + ")"); } } /** * Accept incoming connections on this ZeroTierSocket * @return New ZeroTierSocket representing the accepted connection * @exception IOException when an I/O error occurs */ public ZeroTierSocket accept() throws IOException { if (_zfd < 0) { throw new IOException("Invalid socket (fd < 0)"); } int accetpedFd = -1; ZeroTierSocketAddress addr = new ZeroTierSocketAddress(); if ((accetpedFd = ZeroTierNative.zts_bsd_accept(_zfd, addr)) < 0) { throw new IOException("Error while accepting connection (" + accetpedFd + ")"); } return new ZeroTierSocket(_family, _type, _protocol, accetpedFd); } /** * Close the ZeroTierSocket. * * @exception IOException when an I/O error occurs */ public void close() throws IOException { if (_zfd < 0) { throw new IOException("Invalid socket (fd < 0)"); } ZeroTierNative.zts_bsd_close(_zfd); _isClosed = true; } /** * Return whether keepalive is enabled. * @return true or false * @exception SocketException when an error occurs in the native socket layer */ public boolean getKeepAlive() throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } return ZeroTierNative.zts_get_keepalive(_zfd) == 1; } /** * Get the local port to which this ZeroTierSocket is bound * @return Local port */ public int getLocalPort() { if (! _isBound) { return -1; } return _localPort; } /** * Get the local address to which this ZeroTierSocket is bound * @return Local address */ public InetAddress getLocalAddress() { if (! _isBound) { return null; } return _localAddr; } /** * Get the remote port to which this ZeroTierSocket is bound * @return Remote port */ public int getRemotePort() { if (! _isConnected) { return -1; } return _remotePort; } /** * Get the remote address to which this ZeroTierSocket is bound * @return Remote address */ public InetAddress getRemoteAddress() { if (! _isConnected) { return null; } return _remoteAddr; } /** * Return the size of the receive buffer for the ZeroTierSocket's ZeroTierInputStream (SO_RCVBUF) * @return Size of the receive buffer * @exception SocketException when an error occurs in the native socket layer */ public int getReceiveBufferSize() throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } return ZeroTierNative.zts_get_recv_buf_size(_zfd); } /** * Return the size of the send buffer for the ZeroTierSocket's ZeroTierOutputStream (SO_SNDBUF) * @return Size of the send buffer * @exception SocketException when an error occurs in the native socket layer */ public int getSendBufferSize() throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } return ZeroTierNative.zts_get_send_buf_size(_zfd); } /** * Return whether address reuse is enabled on this ZeroTierSocket (SO_REUSEADDR) * @return true or false * @exception SocketException when an error occurs in the native socket layer */ public boolean getReuseAddressEnabled() throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } return ZeroTierNative.zts_get_reuse_addr(_zfd) == 1; } /** * Return the amount of time that a ZeroTierSocket will linger after closure (SO_LINGER) * @return Nothing. * @exception SocketException when an error occurs in the native socket layer */ public int getSoLingerTime() throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } return ZeroTierNative.zts_get_linger_value(_zfd); } /** * Get the ZeroTierSocket's timeout value (SO_RCVTIMEO) * @return Nothing. * @exception SocketException when an error occurs in the native socket layer */ public int getSoTimeout() throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } return ZeroTierNative.zts_get_recv_timeout(_zfd); } /** * Return whether TCP no-delay is enabled (TCP_NODELAY) * @return true or false * @exception SocketException when an error occurs in the native socket layer */ public boolean tcpNoDelayEnabled() throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } return ZeroTierNative.zts_get_no_delay(_zfd) == 1; } /** * Return whether this ZeroTierSocket is bound to a local address * @return true or false */ public boolean isBound​() { return _isBound; } /** * Return whether this ZeroTierSocket has been closed * @return true or false */ public boolean isClosed​() { return _isClosed; } /** * Return whether this ZeroTierSocket is connected to a remote address * @return true or false */ public boolean isConnected​() { return _isConnected; } /** * Disable the input-aspect of the ZeroTierSocket. * * @exception SocketException when an error occurs in the native socket layer */ public void shutdownInput() throws SocketException { if (! _isConnected) { throw new SocketException("Error: ZeroTierSocket is not connected"); } if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } if (_inputHasBeenShutdown) { throw new SocketException("Error: ZeroTierSocket input has been shut down"); } ZeroTierNative.zts_bsd_shutdown(_zfd, ZeroTierNative.ZTS_SHUT_RD); _inputHasBeenShutdown = true; } /** * Disable the output-aspect of the ZeroTierSocket. * * @exception SocketException when an error occurs in the native socket layer */ public void shutdownOutput() throws SocketException { if (! _isConnected) { throw new SocketException("Error: ZeroTierSocket is not connected"); } if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } if (_outputHasBeenShutdown) { throw new SocketException("Error: ZeroTierSocket output has been shut down"); } ZeroTierNative.zts_bsd_shutdown(_zfd, ZeroTierNative.ZTS_SHUT_WR); _outputHasBeenShutdown = true; } /** * Return a reference to the ZeroTierInputStream used by this ZeroTierSocket * @return A reference to the ZeroTierInputStream * @exception SocketException when an error occurs in the native socket layer */ public ZeroTierInputStream getInputStream() throws SocketException { if (! _isConnected) { throw new SocketException("Error: ZeroTierSocket is not connected"); } if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } if (_inputHasBeenShutdown) { throw new SocketException("Error: ZeroTierSocket input has been shut down"); } return _inputStream; } /** * Return a reference to the ZeroTierOutputStream used by this ZeroTierSocket * @return A reference to the ZeroTierOutputStream * @exception SocketException when an error occurs in the native socket layer */ public ZeroTierOutputStream getOutputStream() throws SocketException { if (! _isConnected) { throw new SocketException("Error: ZeroTierSocket is not connected"); } if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } if (_outputHasBeenShutdown) { throw new SocketException("Error: ZeroTierSocket output has been shut down"); } return _outputStream; } /** * Return whether the input-aspect of the ZeroTierSocket has been disabled. * @return true or false */ public boolean inputStreamHasBeenShutdown​() { return _inputHasBeenShutdown; } /** * Return whether the output-aspect of the ZeroTierSocket has been disabled. * @return true or false */ public boolean outputStreamHasBeenShutdown​() { return _outputHasBeenShutdown; } /** * Enable or disable the keepalive setting (SO_KEEPALIVE) * @param enabled Whether SO_KEEPALIVE is enabled. * * @exception SocketException when an error occurs in the native socket layer */ public void setKeepAliveEnabled(boolean enabled) throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } if (ZeroTierNative.zts_set_keepalive(_zfd, (enabled ? 1 : 0)) != ZeroTierNative.ZTS_ERR_OK) { throw new SocketException("Error: Could not set SO_KEEPALIVE"); } } /** * Set the size of the receive buffer for the ZeroTierSocket's ZeroTierInputStream. * @param bufferSize Size of receive buffer * * @exception SocketException when an error occurs in the native socket layer */ public void setReceiveBufferSize(int bufferSize) throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } if (bufferSize <= 0) { throw new IllegalArgumentException("Error: bufferSize <= 0"); } if (ZeroTierNative.zts_set_recv_buf_size(_zfd, bufferSize) != ZeroTierNative.ZTS_ERR_OK) { throw new SocketException("Error: Could not set receive buffer size"); } } /** * Enable or disable the re-use of addresses (SO_REUSEADDR) * @param enabled Whether SO_REUSEADDR is enabled * * @exception SocketException when an error occurs in the native socket layer */ public void setReuseAddress(boolean enabled) throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } if (ZeroTierNative.zts_set_reuse_addr(_zfd, (enabled ? 1 : 0)) != ZeroTierNative.ZTS_ERR_OK) { throw new SocketException("Error: Could not set SO_REUSEADDR"); } } /** * Set the size of the send buffer for the ZeroTierSocket's ZeroTierOutputStream (SO_SNDBUF) * @param bufferSize Size of send buffer * * @exception SocketException when an error occurs in the native socket layer */ public void setSendBufferSize(int bufferSize) throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } if (bufferSize <= 0) { throw new IllegalArgumentException("Error: bufferSize <= 0"); } if (ZeroTierNative.zts_set_send_buf_size(_zfd, bufferSize) != ZeroTierNative.ZTS_ERR_OK) { throw new SocketException("Error: Could not set SO_SNDBUF"); } } /** * Set the amount of time that a ZeroTierSocket will linger after closure (SO_LINGER) * @param enabled Whether SO_LINGER is enabled * @param lingerTime SO_LINGER time * * @exception SocketException when an error occurs in the native socket layer */ public void setSoLinger(boolean enabled, int lingerTime) throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } if (lingerTime < 0) { throw new IllegalArgumentException("Error: lingerTime < 0"); } if (ZeroTierNative.zts_set_linger(_zfd, (enabled ? 1 : 0), lingerTime) != ZeroTierNative.ZTS_ERR_OK) { throw new SocketException("Error: Could not set ZTS_SO_LINGER"); } } /** * Set the timeout value for SO_RCVTIMEO * @param timeout Socket receive timeout value. * * @exception SocketException when an error occurs in the native socket layer */ public void setSoTimeoutValue(int timeout) throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } if (timeout < 0) { throw new IllegalArgumentException("Error: SO_TIMEOUT < 0"); } // TODO: This is incorrect if (ZeroTierNative.zts_set_recv_timeout(_zfd, timeout, timeout) != ZeroTierNative.ZTS_ERR_OK) { throw new SocketException("Error: Could not set SO_RCVTIMEO"); } } /** * Enable or disable TCP_NODELAY * @param enabled Whether TCP_NODELAY is enabled * * @exception SocketException when an error occurs in the native socket layer */ public void setTcpNoDelayEnabled(boolean enabled) throws SocketException { if (_isClosed) { throw new SocketException("Error: ZeroTierSocket is closed"); } if (ZeroTierNative.zts_set_no_delay(_zfd, (enabled ? 1 : 0)) != ZeroTierNative.ZTS_ERR_OK) { throw new SocketException("Error: Could not set TCP_NODELAY"); } } }