From b9373edbd062b8bd16742d750503933a70f128a9 Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Tue, 14 Jan 2025 12:51:37 +0000 Subject: [PATCH] Use the JDK's built-in support for Unix Domain Sockets on Java 16+ --- utils/socket-utils/build.gradle | 29 +++ .../socket/UnixDomainSocketFactory.java | 13 +- .../common/socket/TunnelingJdkSocket.java | 183 ++++++++++++++++++ 3 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 utils/socket-utils/src/main/java17/datadog/common/socket/TunnelingJdkSocket.java diff --git a/utils/socket-utils/build.gradle b/utils/socket-utils/build.gradle index eb09cca849f..51276c768c1 100644 --- a/utils/socket-utils/build.gradle +++ b/utils/socket-utils/build.gradle @@ -1,8 +1,37 @@ apply from: "$rootDir/gradle/java.gradle" +apply plugin: "idea" + +sourceSets { + main_java17 { + java.srcDirs "${project.projectDir}/src/main/java17" + } +} + +compileMain_java17Java.configure { + setJavaVersion(it, 17) + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} dependencies { + compileOnly sourceSets.main_java17.output + implementation libs.slf4j implementation project(':internal-api') implementation group: 'com.github.jnr', name: 'jnr-unixsocket', version: libs.versions.jnr.unixsocket.get() } + +jar { + from sourceSets.main_java17.output +} + +forbiddenApisMain_java17 { + failOnMissingClasses = false +} + +idea { + module { + jdkName = '17' + } +} diff --git a/utils/socket-utils/src/main/java/datadog/common/socket/UnixDomainSocketFactory.java b/utils/socket-utils/src/main/java/datadog/common/socket/UnixDomainSocketFactory.java index bb1929f369b..bf4001d0a03 100644 --- a/utils/socket-utils/src/main/java/datadog/common/socket/UnixDomainSocketFactory.java +++ b/utils/socket-utils/src/main/java/datadog/common/socket/UnixDomainSocketFactory.java @@ -3,6 +3,7 @@ import static java.util.concurrent.TimeUnit.MINUTES; import datadog.trace.api.Config; +import datadog.trace.api.Platform; import datadog.trace.relocate.api.RatelimitedLogger; import java.io.File; import java.io.IOException; @@ -24,6 +25,8 @@ public final class UnixDomainSocketFactory extends SocketFactory { private static final Logger log = LoggerFactory.getLogger(UnixDomainSocketFactory.class); + private static final boolean JDK_SUPPORTS_UDS = Platform.isJavaVersionAtLeast(16); + private final RatelimitedLogger rlLog = new RatelimitedLogger(log, 5, MINUTES); private final File path; @@ -35,8 +38,14 @@ public UnixDomainSocketFactory(final File path) { @Override public Socket createSocket() throws IOException { try { - final UnixSocketChannel channel = UnixSocketChannel.open(); - return new TunnelingUnixSocket(path, channel); + if (JDK_SUPPORTS_UDS) { + try { + return new TunnelingJdkSocket(path.toPath()); + } catch (Throwable ignore) { + // fall back to jnr-unixsocket library + } + } + return new TunnelingUnixSocket(path, UnixSocketChannel.open()); } catch (Throwable e) { if (Config.get().isAgentConfiguredUsingDefault()) { // fall back to port if we previously auto-discovered this socket file diff --git a/utils/socket-utils/src/main/java17/datadog/common/socket/TunnelingJdkSocket.java b/utils/socket-utils/src/main/java17/datadog/common/socket/TunnelingJdkSocket.java new file mode 100644 index 00000000000..e4c94c91749 --- /dev/null +++ b/utils/socket-utils/src/main/java17/datadog/common/socket/TunnelingJdkSocket.java @@ -0,0 +1,183 @@ +package datadog.common.socket; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnixDomainSocketAddress; +import java.nio.channels.Channels; +import java.nio.channels.SocketChannel; +import java.nio.file.Path; + +/** + * Subtype UNIX socket for a higher-fidelity impersonation of TCP sockets. This is named "tunneling" + * because it assumes the ultimate destination has a hostname and port. + * + *

Bsed on {@link TunnelingUnixSocket}; adapted to use the built-in UDS support added in Java 16. + */ +final class TunnelingJdkSocket extends Socket { + private final SocketAddress unixSocketAddress; + private InetSocketAddress inetSocketAddress; + + private SocketChannel unixSocketChannel; + + private int timeout; + private boolean shutIn; + private boolean shutOut; + private boolean closed; + + TunnelingJdkSocket(final Path path) { + this.unixSocketAddress = UnixDomainSocketAddress.of(path); + } + + TunnelingJdkSocket(final Path path, final InetSocketAddress address) { + this(path); + inetSocketAddress = address; + } + + @Override + public boolean isConnected() { + return null != unixSocketChannel; + } + + @Override + public boolean isInputShutdown() { + return shutIn; + } + + @Override + public boolean isOutputShutdown() { + return shutOut; + } + + @Override + public boolean isClosed() { + return closed; + } + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + if (timeout < 0) { + throw new IllegalArgumentException("Socket timeout can't be negative"); + } + this.timeout = timeout; + } + + @Override + public synchronized int getSoTimeout() throws SocketException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + return timeout; + } + + @Override + public void connect(final SocketAddress endpoint) throws IOException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + if (isConnected()) { + throw new SocketException("Socket is already connected"); + } + inetSocketAddress = (InetSocketAddress) endpoint; + unixSocketChannel = SocketChannel.open(unixSocketAddress); + } + + @Override + public void connect(final SocketAddress endpoint, final int timeout) throws IOException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + if (isConnected()) { + throw new SocketException("Socket is already connected"); + } + inetSocketAddress = (InetSocketAddress) endpoint; + unixSocketChannel = SocketChannel.open(unixSocketAddress); + } + + @Override + public SocketChannel getChannel() { + return unixSocketChannel; + } + + @Override + public InputStream getInputStream() throws IOException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + if (!isConnected()) { + throw new SocketException("Socket is not connected"); + } + if (isInputShutdown()) { + throw new SocketException("Socket input is shutdown"); + } + return Channels.newInputStream(unixSocketChannel); + } + + @Override + public OutputStream getOutputStream() throws IOException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + if (!isConnected()) { + throw new SocketException("Socket is not connected"); + } + if (isInputShutdown()) { + throw new SocketException("Socket output is shutdown"); + } + return Channels.newOutputStream(unixSocketChannel); + } + + @Override + public void shutdownInput() throws IOException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + if (!isConnected()) { + throw new SocketException("Socket is not connected"); + } + if (isInputShutdown()) { + throw new SocketException("Socket input is already shutdown"); + } + unixSocketChannel.shutdownInput(); + shutIn = true; + } + + @Override + public void shutdownOutput() throws IOException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + if (!isConnected()) { + throw new SocketException("Socket is not connected"); + } + if (isOutputShutdown()) { + throw new SocketException("Socket output is already shutdown"); + } + unixSocketChannel.shutdownOutput(); + shutOut = true; + } + + @Override + public InetAddress getInetAddress() { + return inetSocketAddress.getAddress(); + } + + @Override + public void close() throws IOException { + if (isClosed()) { + return; + } + if (null != unixSocketChannel) { + unixSocketChannel.close(); + } + closed = true; + } +}