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() {
}
}
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