October 15, 2019

Java, Let me use the damn FileDescriptor!

Recently I hacked on some utility in Java which should be able to bind low ports like 80 or 443 on demand without running as root. When I needed to bind these port I wanted to execute an external process as root which opens the ports for the main process and then sends the ports via Unix domain socket.

So, for FileOutputStream, RandomAccessFile, etc allow you to get the FileDescriptor. Next up was to find a way to use Unix domain sockets to send the file descriptor. I pretty quickly found the junixsocket library which does exactly that. It allows sending the file descriptors, great!

SendingFileDescriptorExample.java
AFUNIXSocket socket = ...

FileInputStream fin = new FileInputStream(file);
socket.setOutboundFileDescriptors(fin.getFD());
// you can also send more than one FD at the same time, just make sure they're all part of the same call to setOutboundFileDescriptors.

// Ancillary messages are sent _along_ regular in-band messages, so we have to send something here.
os.write("Some message".getBytes("UTF-8"));

Getting the FileDescriptor: Trouble Starts

Now I want onto creating the internet sockets and getting the FileDescriptors out. Well unlike the file classes there seems no official API. The best option I’ve found a getFD method on the ServerSocketChannel implementation. It’s not an official API and you have to break it open with reflection. Ugly ugly!.

GetTheSocketFileDescriptor.java
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.bind(new InetSocketAddress("127.0.0.1", 13423));

// Breaking open the implementation via reflection. Java 11+ will warn you by default about this bad idea
// No grantees to work in future versions
Method getFD = serverSocket.getClass().getMethod("getFD");
getFD.setAccessible(true);
FileDescriptor fd = (FileDescriptor)getFD.invoke(serverSocket);

System.out.println("The FileDescriptor" + fd);

Of course, Java 11+ will warn you about your bad hack:

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by info.gamlor.testing.Main (file:/home/gamlor/hacking/test-test/out/production/test-test/) to method sun.nio.ch.ServerSocketChannelImpl.getFD()
WARNING: Please consider reporting this to the maintainers of info.gamlor.testing.Main
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

Creating a ServerSocket from FileDescriptor, no way?!

Ok, we got the FileDescriptor via reflection and sent it with the junixsocket library. Now we only need to create a ServerSocket or ServerSocketChannel out of the FileDescriptor. Just a call to some constructor or method, right? Right?! Nope, no way as far as I know. The closes is the package local constructor to the ServerSocketChannelImpl:

ServerSocketChannelImpl.java

ServerSocketChannelImpl(SelectorProvider sp, FileDescriptor fd, boolean bound)
    throws IOException
{

I guess with a lot of reflection hacking you could get at it. At this point, I gave up and looked for another solution for my utility. I don’t want a fragile tower of reflection.

Java’s limited FileDescriptor

Rant

Oh, Java please give a way to get the FileDescriptor and create FileDescriptors. It’s like the FileDescriptor class it there to tease you, but not let you do anything useful.

Basically creating Streams, Sockets and Channels from a FileDescriptor is enough. And maybe allow creating a FileDescriptor from a primitive long. Yes, the method would be useless in pure Java, but it would make it easier to use libraries like Java Native Access (JNA) or jnr-ffi by invoking an OS function and passing the FileDescriptor/Handle to the Java layer.

Yes, an app using creating FileDescriptors won’t be a pure Java app. When you do that it’s because you need to integrate into the OS beyond the plain JRE methods.

Anyway, that’s it. For my utility I’m using another approach for now, of course it’s also an OS specific approach =)

Tags: Java