PRD-2078 landed on my desk.
The problem
There are some weird administrators out there, who turn their internal company firewalls into a black-hole. Requests don’t get answered with a clear “no-route-to-host” ICMP-message, nope, they just get swallowed letting the application hang in a limbo waiting for the timeout.
For the Pentaho Report-Designer, this is a bit ugly. Open a file that has a resource from a non-existing host, and you instantly run into problems. In our system, there are many possible sources of network communication: URLs get read, JDBC connections opened, depending on what datasource you use, we may throw in a RMI or raw-socket connection as well. In short: If the network does not play nice, we are hosed.
Phase 1: Avoidance
Now, from a application developer perspective, trying to prevent network connections by tiptoeing around all the places that may trigger network uses is a bit futile. Heck, in the same way you could prevent a economic meltdown by tiptoeing around naming who is broke and who is not.
Preventing reading images from URLs may work, as there are only one or two places where we do that. But even just finding out whether a JDBC driver uses the network, or with which host it may communicate is futile. JDBC URLs are not standardized at all. The standard says “let ’em start with the string “jdbc:”, followed by a vendor specific string. They recommend, that the JDBC URL follows the URL schema. But then, there are tinker-shop companies that just feel that random, undocumented schemas are so much more fun, and present you with URLs like this:
jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS= (PROTOCOL=TCP)(HOST=lcqsol24)(PORT=1521))(ADDRESS=(PROTOCOL=TCP) (HOST=lcqsol25)(PORT=1521))(FAILOVER=on)(LOAD_BALANCE=off)) (CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=snrac)))
Yeah, I always wanted to implement custom parsers for all the JDBC drivers in the world.
And jus for the fun of it, lets assume that I go that way, and cover 90% of the cases. Now all it needs is
* a JNDI defined datasource: JNDI datasources are black-boxes, and we have no way to look into it to see the JDBC-URL
* a Kettle transformation: Hundreds of components able to talk over the network. Some of them even don’t know with whom they communicate until they run.
* a JavaScript or BeanShell Scripting expression: Heck, who knows what’s going on in there?
So slapping workarounds into the code does not help our case. Damn!
Phase 2: Remember thy past
Back in the old ages, when the seas were full of fish and the lion slept next to a sheep without thinking about a tasty meal, we all learned that Java has a unique feature that prevents Applets from connecting to other hosts.That feature was called, a sandbox, enforced by a SecurityManager.
Cool, sounds like an option to me. Slap on a properly configured security manager and we should be ready to go. Implementing a simple security manager that only checks for SocketPermissions and grants everything else was easy. Adding it was easy as well.
But: Once you add a security manager, performance goes down to a crawl. Even if the security manager is an empty implementation, performance is cut in half. When monitoring the process, you will also suddenly notice, that the process spends half of its time in the kernel, and only half of its time working in the user code.
Why?
‘Cause checking permissions is expensive. Code to check permissions looks like that:
SecurityManager m = System.getSecurityManager();
if (m != null)
{
Permission permission = new RuntimePermission("*");
m.checkPermission(permission);
}
Looks innocent. But nonetheless, due to the massive amount of security checks in the JDK, this starts to sum up to a huge performance drag. So matter what we do: Slowing down that much is not an option, as by far more people will complain about bad performance than people complain about bad networking code.
Phase 3: Going low-level: Sockets, SocketImpl and SocketImplFactory
Since the good old days of JDK 1.0, the Socket class was separated from its native implementation, so that the socket-backend can be replaced by a vendor easily. Smells good. Now, if we can go in and provide our own SocketImplFactory, that then creates a filtering wrapper around the existing sockets, we should be ready to go. After all, to talk to the net, you need to create a Socket first.
Half an hour later, after reading through the source code of java.net.Socket and its related classes, I’m sober again. There classes that are there are insufficient to actually create an own Socket wrapper in a reasonable amount of time. Internally, Socket uses SocksSocketImpl, a package protected class in the java.net package. This class provides the ability to talk to the net via proxy-servers. As this class is package-protected, it is not accessible from the outside world. So if we use our own SocketImplFactory, we either have to drop the ability to use Proxy-Servers, or we have to reimplement the various proxy-server protocolls. Yeah, lets implement a insane amount of code just for the filtering.
So we have a theoretical ability to provide a SocketImplFactory, but due to the state of the implementation of the JDK, we cannot use it without reinventing the wheel. Thanks, Sun, for not helping.
Phase 4: Enlightenment though reading code (’cause there is no other high-level documentation)
While reading the socket code, I encountered a class called ProxySelector, which is called most of the time, when creating a socket. This class is intended to select the correct proxy-server for a given address. Well, at that point I’m not choosy any more – if a flying goose with pink elves riding on it would come along and would promise me a solution, I would not hesitate to follow that lead.
Implementing a ProxySelector is easy, installing it is easy, and although it is not designed for filtering, throwing RuntimeExceptions from within that selector is easy too. It feels like the most devilish hacking, with no sense for cleanliness at all – but (to quote Marc Batchelor) “…, but it works!”.
Aftermath
The Proxy-selector is not used for UDP-communication, so the final solution will include the SecurityManager as well. The security-manager part will be disabled by default – there is no need to punish the masses for the misbehaviours of a few sinners. If you have a JDBC-driver that talks via UDP, and you have a paranoid firewall administrator as well, then you will have to tweak a few settings to get it working. But that’s the price you pay for surrounding you with sinners.