Search code examples
javasocketsjna

Determine platform under JNA for setsockopt


I'm writing an implementation of setsockopt under JNA. Java itself supports setsockopt, but it doesn't support all the platform specific socket options. For instance, it doesn't support [TCP_KEEPIDLE][2] under Linux. Clearly, many of these options are not very portable, and using JNA is a route to poor portability; I am aware of this. Please don't bother to tell me the idea is deeply horrible.

What I'd like to do, however, is make my code a little more reuseable than something that just works under Linux. I'd like it to work (as far as is possible) on several target platforms. If the socket option is not available, it can throw an exception.

My challenge is this. The JNA works fine, but the values of the socket options are different across platforms. For instance, SO_RCVBUF is 0x1002 under OS-X and 8 under Linux (I realise SO_RCVBUF is controllable by the normal Java setSockOpt - it's an example that's easy to test with lsof). SO_DONTROUTE is 5 under Linux, and 0x0010 under OS-X (and that isn't controllable via Java setSockOpt).

So what I'd like it to do is to take an enum value representing the socket option (SO_SNDBUF, SO_RCVBUF or whatever), and look that up in a platform dependent map, so I get 0x1002 / 0x010 under OS-X and 8 / 5 under Linux.

That's easy enough, but how do I tell what the platform is under JNA so I know which map to use? JNA must somehow have a sense of its own platform, or it would not (I presume) know how to call the native libraries.

package sockettest;

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.Socket;

import com.sun.jna.LastErrorException;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;

public class JNASockOpt {

    private static Field fdField;
    static {
        Native.register("c");
        try {
            fdField = FileDescriptor.class.getDeclaredField("fd");
            fdField.setAccessible(true);
        } catch (Exception ex) {
            fdField = null;
        }
    }

    public static int getInputFd(Socket s) {
        try {
            FileInputStream in = (FileInputStream)s.getInputStream();
            FileDescriptor fd = in.getFD();
            return fdField.getInt(fd);
        } catch (Exception e) { }
        return -1;
    }

    public static int getOutputFd(Socket s) {
        try {
            FileOutputStream in = (FileOutputStream)s.getOutputStream();
            FileDescriptor fd = in.getFD();
            return fdField.getInt(fd);
        } catch (Exception e) { }
        return -1;
    }

    public static int getFd(Socket s) {
        int fd = getInputFd(s);
        if (fd != -1)
            return fd;
        return getOutputFd(s);
    }

    // The list of SOL_ and SO_ options is platform dependent
    public static final int SOL_SOCKET = 0xffff; // that's under OS-X, but it's 1 under Linux
    public static final int SO_RCVBUF = 0x1002; // that's under OS-X, but it's 8 under Linux
    public static final int SO_DONTROUTE = 0x0010; // that's under OS-X, but it's 5 under Linux

    private static native int setsockopt(int fd, int level, int option_name, Pointer option_value, int option_len) throws LastErrorException;

    public static void setSockOpt (Socket socket, int level, int option_name, int option_value) throws IOException {
        if (socket == null)
            throw new IOException("Null socket");
        int fd = getFd(socket);
        if (fd == -1)
            throw new IOException("Bad socket FD");
        IntByReference val = new IntByReference(option_value);
        try {
            setsockopt(fd, level, option_name, val.getPointer(), 4);
        } catch (LastErrorException ex) {
            throw new IOException("setsockopt: " + strerror(ex.getErrorCode()));
        }
    }

    public static native String strerror(int errnum);

    private JNASockOpt() {
    }
}

Solution

  • jnaplatform does this by string parsing System.getProperty("os.name");, which seems pretty horrible to me, but if jnaplatform does it, I guess that should be good enough for me.

    Results (i.e. how I used the above idea to solve the issue in the question) at https://github.com/abligh/jnasockopt - specifically https://github.com/abligh/jnasockopt/blob/master/src/org/jnasockopt/JNASockOptionDetails.java