Skip to content

Commit 46cfe8e

Browse files
committed
Use the JDK's built-in support for Unix Domain Sockets on Java 16+
1 parent b0770e9 commit 46cfe8e

File tree

3 files changed

+225
-2
lines changed

3 files changed

+225
-2
lines changed

utils/socket-utils/build.gradle

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,37 @@
11
apply from: "$rootDir/gradle/java.gradle"
2+
apply plugin: "idea"
3+
4+
sourceSets {
5+
main_java17 {
6+
java.srcDirs "${project.projectDir}/src/main/java17"
7+
}
8+
}
9+
10+
compileMain_java17Java.configure {
11+
setJavaVersion(it, 17)
12+
sourceCompatibility = JavaVersion.VERSION_1_8
13+
targetCompatibility = JavaVersion.VERSION_1_8
14+
}
215

316
dependencies {
17+
compileOnly sourceSets.main_java17.output
18+
419
implementation libs.slf4j
520
implementation project(':internal-api')
621

722
implementation group: 'com.github.jnr', name: 'jnr-unixsocket', version: libs.versions.jnr.unixsocket.get()
823
}
24+
25+
jar {
26+
from sourceSets.main_java17.output
27+
}
28+
29+
forbiddenApisMain_java17 {
30+
failOnMissingClasses = false
31+
}
32+
33+
idea {
34+
module {
35+
jdkName = '17'
36+
}
37+
}

utils/socket-utils/src/main/java/datadog/common/socket/UnixDomainSocketFactory.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static java.util.concurrent.TimeUnit.MINUTES;
44

55
import datadog.trace.api.Config;
6+
import datadog.trace.api.Platform;
67
import datadog.trace.relocate.api.RatelimitedLogger;
78
import java.io.File;
89
import java.io.IOException;
@@ -24,6 +25,8 @@
2425
public final class UnixDomainSocketFactory extends SocketFactory {
2526
private static final Logger log = LoggerFactory.getLogger(UnixDomainSocketFactory.class);
2627

28+
private static final boolean JDK_HAS_UDS = Platform.isJavaVersionAtLeast(16);
29+
2730
private final RatelimitedLogger rlLog = new RatelimitedLogger(log, 5, MINUTES);
2831

2932
private final File path;
@@ -35,8 +38,14 @@ public UnixDomainSocketFactory(final File path) {
3538
@Override
3639
public Socket createSocket() throws IOException {
3740
try {
38-
final UnixSocketChannel channel = UnixSocketChannel.open();
39-
return new TunnelingUnixSocket(path, channel);
41+
if (JDK_HAS_UDS) {
42+
try {
43+
return new TunnelingJdkSocket(path.toPath());
44+
} catch (Throwable ignore) {
45+
// fall back to jnr-unixsocket library
46+
}
47+
}
48+
return new TunnelingUnixSocket(path, UnixSocketChannel.open());
4049
} catch (Throwable e) {
4150
if (Config.get().isAgentConfiguredUsingDefault()) {
4251
// fall back to port if we previously auto-discovered this socket file
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package datadog.common.socket;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.io.OutputStream;
6+
import java.net.InetAddress;
7+
import java.net.InetSocketAddress;
8+
import java.net.Socket;
9+
import java.net.SocketAddress;
10+
import java.net.SocketException;
11+
import java.net.UnixDomainSocketAddress;
12+
import java.nio.channels.Channels;
13+
import java.nio.channels.SocketChannel;
14+
import java.nio.file.Path;
15+
16+
/**
17+
* Subtype UNIX socket for a higher-fidelity impersonation of TCP sockets. This is named "tunneling"
18+
* because it assumes the ultimate destination has a hostname and port.
19+
*
20+
* <p>Copied from <a
21+
* href="https://github.com/square/okhttp/blob/master/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainServerSocketFactory.java">okHttp
22+
* examples</a>.
23+
*/
24+
final class TunnelingJdkSocket extends Socket {
25+
private final SocketAddress unixSocketAddress;
26+
private InetSocketAddress inetSocketAddress;
27+
28+
private SocketChannel unixSocketChannel;
29+
30+
private int timeout;
31+
private boolean shutIn;
32+
private boolean shutOut;
33+
private boolean closed;
34+
35+
TunnelingJdkSocket(final Path path) {
36+
this.unixSocketAddress = UnixDomainSocketAddress.of(path);
37+
}
38+
39+
TunnelingJdkSocket(final Path path, final InetSocketAddress address) {
40+
this(path);
41+
inetSocketAddress = address;
42+
}
43+
44+
@Override
45+
public boolean isConnected() {
46+
return null != unixSocketChannel;
47+
}
48+
49+
@Override
50+
public boolean isInputShutdown() {
51+
return shutIn;
52+
}
53+
54+
@Override
55+
public boolean isOutputShutdown() {
56+
return shutOut;
57+
}
58+
59+
@Override
60+
public boolean isClosed() {
61+
return closed;
62+
}
63+
64+
@Override
65+
public synchronized void setSoTimeout(int timeout) throws SocketException {
66+
if (isClosed()) {
67+
throw new SocketException("Socket is closed");
68+
}
69+
if (timeout < 0) {
70+
throw new IllegalArgumentException("Socket timeout can't be negative");
71+
}
72+
this.timeout = timeout;
73+
}
74+
75+
@Override
76+
public synchronized int getSoTimeout() throws SocketException {
77+
if (isClosed()) {
78+
throw new SocketException("Socket is closed");
79+
}
80+
return timeout;
81+
}
82+
83+
@Override
84+
public void connect(final SocketAddress endpoint) throws IOException {
85+
if (isClosed()) {
86+
throw new SocketException("Socket is closed");
87+
}
88+
if (isConnected()) {
89+
throw new SocketException("Socket is already connected");
90+
}
91+
inetSocketAddress = (InetSocketAddress) endpoint;
92+
unixSocketChannel = SocketChannel.open(unixSocketAddress);
93+
}
94+
95+
@Override
96+
public void connect(final SocketAddress endpoint, final int timeout) throws IOException {
97+
if (isClosed()) {
98+
throw new SocketException("Socket is closed");
99+
}
100+
if (isConnected()) {
101+
throw new SocketException("Socket is already connected");
102+
}
103+
inetSocketAddress = (InetSocketAddress) endpoint;
104+
unixSocketChannel = SocketChannel.open(unixSocketAddress);
105+
}
106+
107+
@Override
108+
public SocketChannel getChannel() {
109+
return unixSocketChannel;
110+
}
111+
112+
@Override
113+
public InputStream getInputStream() throws IOException {
114+
if (isClosed()) {
115+
throw new SocketException("Socket is closed");
116+
}
117+
if (!isConnected()) {
118+
throw new SocketException("Socket is not connected");
119+
}
120+
if (isInputShutdown()) {
121+
throw new SocketException("Socket input is shutdown");
122+
}
123+
return Channels.newInputStream(unixSocketChannel);
124+
}
125+
126+
@Override
127+
public OutputStream getOutputStream() throws IOException {
128+
if (isClosed()) {
129+
throw new SocketException("Socket is closed");
130+
}
131+
if (!isConnected()) {
132+
throw new SocketException("Socket is not connected");
133+
}
134+
if (isInputShutdown()) {
135+
throw new SocketException("Socket output is shutdown");
136+
}
137+
return Channels.newOutputStream(unixSocketChannel);
138+
}
139+
140+
@Override
141+
public void shutdownInput() throws IOException {
142+
if (isClosed()) {
143+
throw new SocketException("Socket is closed");
144+
}
145+
if (!isConnected()) {
146+
throw new SocketException("Socket is not connected");
147+
}
148+
if (isInputShutdown()) {
149+
throw new SocketException("Socket input is already shutdown");
150+
}
151+
unixSocketChannel.shutdownInput();
152+
shutIn = true;
153+
}
154+
155+
@Override
156+
public void shutdownOutput() throws IOException {
157+
if (isClosed()) {
158+
throw new SocketException("Socket is closed");
159+
}
160+
if (!isConnected()) {
161+
throw new SocketException("Socket is not connected");
162+
}
163+
if (isOutputShutdown()) {
164+
throw new SocketException("Socket output is already shutdown");
165+
}
166+
unixSocketChannel.shutdownOutput();
167+
shutOut = true;
168+
}
169+
170+
@Override
171+
public InetAddress getInetAddress() {
172+
return inetSocketAddress.getAddress();
173+
}
174+
175+
@Override
176+
public void close() throws IOException {
177+
if (isClosed()) {
178+
return;
179+
}
180+
if (null != unixSocketChannel) {
181+
unixSocketChannel.close();
182+
}
183+
closed = true;
184+
}
185+
}

0 commit comments

Comments
 (0)