Not able to intercept java.net.Socket class constructor
@raphw Pleas help.
We’re facing an issue where our code creates a large number of sockets, and once we hit the OS limit for file descriptors, the program shuts down. We increased the limit using ulimit -n 65535, but it still feels like a ticking time bomb.
The codebase is large, and we’re not able to find who is creating the sockets or from where. We use Apache HttpClient and many other third-party libraries, so it’s possible a third-party library is doing this.
lsof -p <OUR_CODE_PID> output
lsof -p <OUR_CODE_PID>
# Thousands of sockets like the following are being created:
java <OUR_CODE_PID> <user> <FILE_DESCRIPTOR>u sock(<TYPE_OF_DESCRIPTOR>) 0,9 0t0 <KERNEL_SOCKET_INODE> protocol: TCP
We tried to gather more information using OS commands, but the OS reports these as UNBOUND/EPHEMERAL and we can’t get any additional details.
We support Java 17, but the current version runs on Java 8.
We attempted to write a java.net.Socket interceptor using byte-buddy-agent-1.10.0.jar and byte-buddy-dep-1.10.0.jar to intercept the java.net.Socket constructor, but it isn’t working.
We tested the same code against a custom class and it works fine.
Our research suggests that java.net.Socket is a bootstrap class, so we need to inject the agent into the bootstrap class loader. We tried that as well, but it’s still not working.
Can someone point us in the right direction?
SocketCreationAgent
import java.io.File;
import java.lang.instrument.Instrumentation;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;
public class SocketCreationAgent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("SocketCreationAgent loaded");
// WORKS (custom class)
new AgentBuilder.Default()
.ignore(ElementMatchers.none())
.type(ElementMatchers.named("com.soa.socket.interceptor.agent.test.InterceptThisClass"))
.transform((builder, typeDescription, classLoader, module) -> {
System.out.println("Transforming class: " + typeDescription.getName());
return builder.constructor(ElementMatchers.any())
.intercept(Advice.to(SocketInterceptorAdvice.class));
})
.installOn(inst);
// DOES NOT WORK (bootstrap class)
new AgentBuilder.Default()
.ignore(ElementMatchers.none())
.type(ElementMatchers.named("java.net.Socket"))
.transform((builder, typeDescription, classLoader, module) -> {
System.out.println("Transforming class: " + typeDescription.getName());
return builder.constructor(ElementMatchers.any())
.intercept(Advice.to(SocketInterceptorAdvice.class));
})
.installOn(inst);
// DOES NOT WORK (with bootstrap injection)
// Path to the agent JAR that includes SocketInterceptorAdvice.class
File agentJar = new File("socket.interceptor-0.0.1-SNAPSHOT.jar");
new AgentBuilder.Default()
.ignore(ElementMatchers.none())
.enableBootstrapInjection(inst, agentJar) // allow bootstrap class loader to see advice
.type(ElementMatchers.named("java.net.Socket"))
.transform((builder, typeDescription, classLoader, module) -> {
System.out.println("Transforming java.net.Socket");
return builder.constructor(ElementMatchers.any())
.intercept(Advice.to(SocketInterceptorAdvice.class));
})
.installOn(inst);
}
}
SocketInterceptorAdvice
import net.bytebuddy.asm.Advice;
public class SocketInterceptorAdvice {
@Advice.OnMethodEnter
public static void onEnter() {
System.out.println("SocketInterceptorAdvice: constructor called");
Thread.dumpStack();
}
@Advice.OnMethodExit
public static void onExit() {
System.out.println("SocketInterceptorAdvice: constructor finished");
}
}
InterceptThisClass
public class InterceptThisClass {
public InterceptThisClass() {
System.out.println("In Test Constructor.");
}
}
SocketInterceptorTest
import java.io.IOException;
import java.net.Socket;
public class SocketInterceptorTest {
public static void main(String[] args) throws IOException {
// Open a few sockets to test interception
for (int i = 0; i < 3; i++) {
InterceptThisClass t = new InterceptThisClass();
Socket socket = new Socket();
// Socket socket = new Socket("google.com", 80);
}
}
}
Running the test
java -javaagent:/<path>/socket.interceptor-0.0.1-SNAPSHOT.jar SocketInterceptorTest
Sample output which works for custom class but not for java.net.Socket
SocketCreationAgent loaded
Transforming class: socket.interceptor.agent.test.InterceptThisClass
SocketInterceptorAdvice: constructor called
java.lang.Exception: Stack trace
at java.lang.Thread.dumpStack(Thread.java:1336)
at socket.interceptor.agent.test.InterceptThisClass.<init>(InterceptThisClass.java)
at socket.interceptor.agent.test.SocketInterceptorTest.main(SocketInterceptorTest.java:11)
In Test Constructor.
SocketInterceptorAdvice: constructor finished
Transforming class: java.net.Socket
Transforming java.net.Socket
SocketInterceptorAdvice: constructor called
java.lang.Exception: Stack trace
at java.lang.Thread.dumpStack(Thread.java:1336)
at socket.interceptor.agent.test.InterceptThisClass.<init>(InterceptThisClass.java)
at socket.interceptor.agent.test.SocketInterceptorTest.main(SocketInterceptorTest.java:11)
In Test Constructor.
SocketInterceptorAdvice: constructor finished
SocketInterceptorAdvice: constructor called
java.lang.Exception: Stack trace
at java.lang.Thread.dumpStack(Thread.java:1336)
at socket.interceptor.agent.test.InterceptThisClass.<init>(InterceptThisClass.java)
at socket.interceptor.agent.test.SocketInterceptorTest.main(SocketInterceptorTest.java:11)
In Test Constructor.
SocketInterceptorAdvice: constructor finished
You need to use Advice as a visitor:
builder.visit(Advice.to(SocketInterceptorAdvice.class).on(isConstructor())
Also, you need to activate retransformation:
agentBuilder.disableClassFormatChanges().with(RetransformationStrategy.RETRANSFORM)
You do not need bootstrap injection.
@raphw It worked. Thank you very much for saving my day. I am putting complete code here if anyone in future has same requirement.
RetransformationStrategy.RETRANSFORM -> RedefinitionStrategy.RETRANSFORMATION
isConstructor() -> ElementMatchers.isConstructor()
public class SocketCreationAgent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("SocketCreationAgent loaded");
new AgentBuilder.Default().disableClassFormatChanges().with(RedefinitionStrategy.RETRANSFORMATION).ignore(ElementMatchers.none())
.type(ElementMatchers.named("java.net.Socket")).transform((builder, typeDescription, classLoader, module) -> {
System.out.println("Transforming class: " + typeDescription.getName());
return builder.visit(Advice.to(InterceptorAdvice.class).on(ElementMatchers.isConstructor()));
}).installOn(inst);
}
}
public class SocketInterceptorAdvice {
@Advice.OnMethodEnter
public static void onEnter(@Advice.AllArguments Object[] args, @Advice.Origin String sig) {
System.out.println("\n############ " + sig + " START ############.");
System.out.println("\n## Args START ##.");
if (args != null && args.length > 0) {
StringBuilder sb = new StringBuilder(" Args: ");
for (Object arg : args) {
sb.append(arg).append(" ");
}
System.out.println(sb.toString());
}
System.out.println("## Args END ##.\n");
System.out.println("\n## Callstack START ##.");
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
for (int i = 1; i < elements.length; i++) {
StackTraceElement s = elements[i];
System.out.println("\tat " + s.getClassName() + "." + s.getMethodName() + "(" + s.getFileName() + ":" + s.getLineNumber() + ")");
}
System.out.println("\n############ " + sig + " END ############.");
}
@Advice.OnMethodExit
public static void onExit() {
System.out.println("############ constructor() END ############.\n");
}
}
public class SocketInterceptorTest {
public static void main(String[] args) throws IOException {
// Open a few sockets to test interception
for (int i = 1; i <= 3; i++) {
Socket socket = new Socket();
// Socket socket = new Socket("google.com", 80);
}
}
}
@raphw I tried getting the constructed object in onExit method using @Advice.This Socket socket and it is not intercepting it anymore. I remove @Advice.This Socket socket and it works.
What am I doing wrong?
@Advice.OnMethodExit
public static void onExit(@Advice.This Socket socket) {
System.out.println("############ constructor() END ############.\n");
}
I got solution for above. @Advice.This Object thisObject worked.