Skip to content

Commit f17db33

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

File tree

3 files changed

+224
-2
lines changed

3 files changed

+224
-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_SUPPORTS_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_SUPPORTS_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: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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>Bsed on {@link TunnelingUnixSocket} but adapted to use the built-in UDS support added in Java
21+
* 16.
22+
*/
23+
final class TunnelingJdkSocket extends Socket {
24+
private final SocketAddress unixSocketAddress;
25+
private InetSocketAddress inetSocketAddress;
26+
27+
private SocketChannel unixSocketChannel;
28+
29+
private int timeout;
30+
private boolean shutIn;
31+
private boolean shutOut;
32+
private boolean closed;
33+
34+
TunnelingJdkSocket(final Path path) {
35+
this.unixSocketAddress = UnixDomainSocketAddress.of(path);
36+
}
37+
38+
TunnelingJdkSocket(final Path path, final InetSocketAddress address) {
39+
this(path);
40+
inetSocketAddress = address;
41+
}
42+
43+
@Override
44+
public boolean isConnected() {
45+
return null != unixSocketChannel;
46+
}
47+
48+
@Override
49+
public boolean isInputShutdown() {
50+
return shutIn;
51+
}
52+
53+
@Override
54+
public boolean isOutputShutdown() {
55+
return shutOut;
56+
}
57+
58+
@Override
59+
public boolean isClosed() {
60+
return closed;
61+
}
62+
63+
@Override
64+
public synchronized void setSoTimeout(int timeout) throws SocketException {
65+
if (isClosed()) {
66+
throw new SocketException("Socket is closed");
67+
}
68+
if (timeout < 0) {
69+
throw new IllegalArgumentException("Socket timeout can't be negative");
70+
}
71+
this.timeout = timeout;
72+
}
73+
74+
@Override
75+
public synchronized int getSoTimeout() throws SocketException {
76+
if (isClosed()) {
77+
throw new SocketException("Socket is closed");
78+
}
79+
return timeout;
80+
}
81+
82+
@Override
83+
public void connect(final SocketAddress endpoint) throws IOException {
84+
if (isClosed()) {
85+
throw new SocketException("Socket is closed");
86+
}
87+
if (isConnected()) {
88+
throw new SocketException("Socket is already connected");
89+
}
90+
inetSocketAddress = (InetSocketAddress) endpoint;
91+
unixSocketChannel = SocketChannel.open(unixSocketAddress);
92+
}
93+
94+
@Override
95+
public void connect(final SocketAddress endpoint, final int timeout) throws IOException {
96+
if (isClosed()) {
97+
throw new SocketException("Socket is closed");
98+
}
99+
if (isConnected()) {
100+
throw new SocketException("Socket is already connected");
101+
}
102+
inetSocketAddress = (InetSocketAddress) endpoint;
103+
unixSocketChannel = SocketChannel.open(unixSocketAddress);
104+
}
105+
106+
@Override
107+
public SocketChannel getChannel() {
108+
return unixSocketChannel;
109+
}
110+
111+
@Override
112+
public InputStream getInputStream() throws IOException {
113+
if (isClosed()) {
114+
throw new SocketException("Socket is closed");
115+
}
116+
if (!isConnected()) {
117+
throw new SocketException("Socket is not connected");
118+
}
119+
if (isInputShutdown()) {
120+
throw new SocketException("Socket input is shutdown");
121+
}
122+
return Channels.newInputStream(unixSocketChannel);
123+
}
124+
125+
@Override
126+
public OutputStream getOutputStream() throws IOException {
127+
if (isClosed()) {
128+
throw new SocketException("Socket is closed");
129+
}
130+
if (!isConnected()) {
131+
throw new SocketException("Socket is not connected");
132+
}
133+
if (isInputShutdown()) {
134+
throw new SocketException("Socket output is shutdown");
135+
}
136+
return Channels.newOutputStream(unixSocketChannel);
137+
}
138+
139+
@Override
140+
public void shutdownInput() throws IOException {
141+
if (isClosed()) {
142+
throw new SocketException("Socket is closed");
143+
}
144+
if (!isConnected()) {
145+
throw new SocketException("Socket is not connected");
146+
}
147+
if (isInputShutdown()) {
148+
throw new SocketException("Socket input is already shutdown");
149+
}
150+
unixSocketChannel.shutdownInput();
151+
shutIn = true;
152+
}
153+
154+
@Override
155+
public void shutdownOutput() throws IOException {
156+
if (isClosed()) {
157+
throw new SocketException("Socket is closed");
158+
}
159+
if (!isConnected()) {
160+
throw new SocketException("Socket is not connected");
161+
}
162+
if (isOutputShutdown()) {
163+
throw new SocketException("Socket output is already shutdown");
164+
}
165+
unixSocketChannel.shutdownOutput();
166+
shutOut = true;
167+
}
168+
169+
@Override
170+
public InetAddress getInetAddress() {
171+
return inetSocketAddress.getAddress();
172+
}
173+
174+
@Override
175+
public void close() throws IOException {
176+
if (isClosed()) {
177+
return;
178+
}
179+
if (null != unixSocketChannel) {
180+
unixSocketChannel.close();
181+
}
182+
closed = true;
183+
}
184+
}

0 commit comments

Comments
 (0)