8072384: Setting IP_TOS on java.net sockets not working on unix
authorcoffeys
Thu, 04 Jun 2015 18:16:25 +0100
changeset 30963 88469d06e03f
parent 30962 3d58028e8e7f
child 30965 2d5e4788ee21
8072384: Setting IP_TOS on java.net sockets not working on unix Reviewed-by: michaelm
jdk/make/mapfiles/libnet/mapfile-vers
jdk/src/java.base/share/classes/java/net/AbstractPlainSocketImpl.java
jdk/src/java.base/share/classes/java/net/DatagramSocket.java
jdk/src/java.base/share/classes/java/net/Socket.java
jdk/src/java.base/unix/classes/java/net/PlainDatagramSocketImpl.java
jdk/src/java.base/unix/classes/java/net/PlainSocketImpl.java
jdk/src/java.base/unix/native/libnet/PlainDatagramSocketImpl.c
jdk/src/java.base/unix/native/libnet/PlainSocketImpl.c
jdk/src/java.base/unix/native/libnet/net_util_md.c
jdk/test/java/net/SocketOption/OptionsTest.java
--- a/jdk/make/mapfiles/libnet/mapfile-vers	Thu Jun 04 10:27:06 2015 +0100
+++ b/jdk/make/mapfiles/libnet/mapfile-vers	Thu Jun 04 18:16:25 2015 +0100
@@ -42,7 +42,7 @@
 		Java_java_net_Inet4Address_init;
 		Java_java_net_Inet6Address_init;
 		Java_java_net_PlainDatagramSocketImpl_setTTL;
-		Java_java_net_PlainDatagramSocketImpl_socketSetOption;
+		Java_java_net_PlainDatagramSocketImpl_socketSetOption0;
 		Java_java_net_PlainDatagramSocketImpl_bind0;
 		Java_java_net_PlainSocketImpl_socketAccept;
 		Java_java_net_DatagramPacket_init;
@@ -73,7 +73,7 @@
 		Java_java_net_SocketOutputStream_init;
 		Java_java_net_PlainDatagramSocketImpl_peek;
 		Java_java_net_PlainDatagramSocketImpl_peekData;
-		Java_java_net_PlainSocketImpl_socketSetOption;
+		Java_java_net_PlainSocketImpl_socketSetOption0;
 		Java_java_net_PlainSocketImpl_socketSendUrgentData;
 		Java_java_net_PlainDatagramSocketImpl_datagramSocketCreate;
 		Java_java_net_PlainSocketImpl_socketGetOption;
--- a/jdk/src/java.base/share/classes/java/net/AbstractPlainSocketImpl.java	Thu Jun 04 10:27:06 2015 +0100
+++ b/jdk/src/java.base/share/classes/java/net/AbstractPlainSocketImpl.java	Thu Jun 04 18:16:25 2015 +0100
@@ -312,11 +312,16 @@
             ret = socketGetOption(opt, null);
             return ret;
         case IP_TOS:
-            ret = socketGetOption(opt, null);
-            if (ret == -1) { // ipv6 tos
-                return trafficClass;
-            } else {
-                return ret;
+            try {
+                ret = socketGetOption(opt, null);
+                if (ret == -1) { // ipv6 tos
+                    return trafficClass;
+                } else {
+                    return ret;
+                }
+            } catch (SocketException se) {
+                    // TODO - should make better effort to read TOS or TCLASS
+                    return trafficClass; // ipv6 tos
             }
         case SO_KEEPALIVE:
             ret = socketGetOption(opt, null);
--- a/jdk/src/java.base/share/classes/java/net/DatagramSocket.java	Thu Jun 04 10:27:06 2015 +0100
+++ b/jdk/src/java.base/share/classes/java/net/DatagramSocket.java	Thu Jun 04 18:16:25 2015 +0100
@@ -1184,7 +1184,14 @@
 
         if (isClosed())
             throw new SocketException("Socket is closed");
-        getImpl().setOption(SocketOptions.IP_TOS, tc);
+        try {
+            getImpl().setOption(SocketOptions.IP_TOS, tc);
+        } catch (SocketException se) {
+            // not supported if socket already connected
+            // Solaris returns error in such cases
+            if(!isConnected())
+                throw se;
+        }
     }
 
     /**
--- a/jdk/src/java.base/share/classes/java/net/Socket.java	Thu Jun 04 10:27:06 2015 +0100
+++ b/jdk/src/java.base/share/classes/java/net/Socket.java	Thu Jun 04 18:16:25 2015 +0100
@@ -1380,7 +1380,14 @@
 
         if (isClosed())
             throw new SocketException("Socket is closed");
-        getImpl().setOption(SocketOptions.IP_TOS, tc);
+        try {
+            getImpl().setOption(SocketOptions.IP_TOS, tc);
+        } catch (SocketException se) {
+            // not supported if socket already connected
+            // Solaris returns error in such cases
+            if(!isConnected())
+                throw se;
+        }
     }
 
     /**
--- a/jdk/src/java.base/unix/classes/java/net/PlainDatagramSocketImpl.java	Thu Jun 04 10:27:06 2015 +0100
+++ b/jdk/src/java.base/unix/classes/java/net/PlainDatagramSocketImpl.java	Thu Jun 04 18:16:25 2015 +0100
@@ -80,6 +80,15 @@
         return options;
     }
 
+    protected void socketSetOption(int opt, Object val) throws SocketException {
+        try {
+            socketSetOption0(opt, val);
+        } catch (SocketException se) {
+            if (!connected)
+                throw se;
+        }
+    }
+
     protected synchronized native void bind0(int lport, InetAddress laddr)
         throws SocketException;
 
@@ -112,7 +121,7 @@
 
     protected native void datagramSocketClose();
 
-    protected native void socketSetOption(int opt, Object val)
+    protected native void socketSetOption0(int opt, Object val)
         throws SocketException;
 
     protected native Object socketGetOption(int opt) throws SocketException;
--- a/jdk/src/java.base/unix/classes/java/net/PlainSocketImpl.java	Thu Jun 04 10:27:06 2015 +0100
+++ b/jdk/src/java.base/unix/classes/java/net/PlainSocketImpl.java	Thu Jun 04 18:16:25 2015 +0100
@@ -94,6 +94,15 @@
         return options;
     }
 
+    protected void socketSetOption(int opt, boolean b, Object val) throws SocketException {
+        try {
+            socketSetOption0(opt, b, val);
+        } catch (SocketException se) {
+            if (socket == null || !socket.isConnected())
+                throw se;
+        }
+    }
+
     native void socketCreate(boolean isServer) throws IOException;
 
     native void socketConnect(InetAddress address, int port, int timeout)
@@ -114,7 +123,7 @@
 
     static native void initProto();
 
-    native void socketSetOption(int cmd, boolean on, Object value)
+    native void socketSetOption0(int cmd, boolean on, Object value)
         throws SocketException;
 
     native int socketGetOption(int opt, Object iaContainerObj) throws SocketException;
--- a/jdk/src/java.base/unix/native/libnet/PlainDatagramSocketImpl.c	Thu Jun 04 10:27:06 2015 +0100
+++ b/jdk/src/java.base/unix/native/libnet/PlainDatagramSocketImpl.c	Thu Jun 04 18:16:25 2015 +0100
@@ -1294,11 +1294,11 @@
 
 /*
  * Class:     java_net_PlainDatagramSocketImpl
- * Method:    socketSetOption
+ * Method:    socketSetOption0
  * Signature: (ILjava/lang/Object;)V
  */
 JNIEXPORT void JNICALL
-Java_java_net_PlainDatagramSocketImpl_socketSetOption(JNIEnv *env,
+Java_java_net_PlainDatagramSocketImpl_socketSetOption0(JNIEnv *env,
                                                       jobject this,
                                                       jint opt,
                                                       jobject value) {
--- a/jdk/src/java.base/unix/native/libnet/PlainSocketImpl.c	Thu Jun 04 10:27:06 2015 +0100
+++ b/jdk/src/java.base/unix/native/libnet/PlainSocketImpl.c	Thu Jun 04 18:16:25 2015 +0100
@@ -847,11 +847,11 @@
 
 /*
  * Class:     java_net_PlainSocketImpl
- * Method:    socketSetOption
+ * Method:    socketSetOption0
  * Signature: (IZLjava/lang/Object;)V
  */
 JNIEXPORT void JNICALL
-Java_java_net_PlainSocketImpl_socketSetOption(JNIEnv *env, jobject this,
+Java_java_net_PlainSocketImpl_socketSetOption0(JNIEnv *env, jobject this,
                                               jint cmd, jboolean on,
                                               jobject value) {
     int fd;
--- a/jdk/src/java.base/unix/native/libnet/net_util_md.c	Thu Jun 04 10:27:06 2015 +0100
+++ b/jdk/src/java.base/unix/native/libnet/net_util_md.c	Thu Jun 04 18:16:25 2015 +0100
@@ -1023,12 +1023,10 @@
 
     int i;
 
-    /*
-     * Different multicast options if IPv6 is enabled
-     */
 #ifdef AF_INET6
     if (ipv6_available()) {
         switch (cmd) {
+            // Different multicast options if IPv6 is enabled
             case java_net_SocketOptions_IP_MULTICAST_IF:
             case java_net_SocketOptions_IP_MULTICAST_IF2:
                 *level = IPPROTO_IPV6;
@@ -1039,6 +1037,13 @@
                 *level = IPPROTO_IPV6;
                 *optname = IPV6_MULTICAST_LOOP;
                 return 0;
+#if (defined(__solaris__) || defined(MACOSX))
+            // Map IP_TOS request to IPV6_TCLASS
+            case java_net_SocketOptions_IP_TOS:
+                *level = IPPROTO_IPV6;
+                *optname = IPV6_TCLASS;
+                return 0;
+#endif
         }
     }
 #endif
@@ -1214,9 +1219,6 @@
  * Wrapper for getsockopt system routine - does any necessary
  * pre/post processing to deal with OS specific oddities :-
  *
- * IP_TOS is a no-op with IPv6 sockets as it's setup when
- * the connection is established.
- *
  * On Linux the SO_SNDBUF/SO_RCVBUF values must be post-processed
  * to compensate for an incorrect value returned by the kernel.
  */
@@ -1227,21 +1229,6 @@
     int rv;
     socklen_t socklen = *len;
 
-#ifdef AF_INET6
-    if ((level == IPPROTO_IP) && (opt == IP_TOS)) {
-        if (ipv6_available()) {
-
-            /*
-             * For IPv6 socket option implemented at Java-level
-             * so return -1.
-             */
-            int *tc = (int *)result;
-            *tc = -1;
-            return 0;
-        }
-    }
-#endif
-
     rv = getsockopt(fd, level, opt, result, &socklen);
     *len = socklen;
 
@@ -1285,8 +1272,7 @@
  *
  * For IP_TOS socket option need to mask off bits as this
  * aren't automatically masked by the kernel and results in
- * an error. In addition IP_TOS is a NOOP with IPv6 as it
- * should be setup as connection time.
+ * an error.
  */
 int
 NET_SetSockOpt(int fd, int level, int  opt, const void *arg,
@@ -1317,9 +1303,9 @@
 
     /*
      * IPPROTO/IP_TOS :-
-     * 1. IPv6 on Solaris/Mac OS: NOOP and will be set
-     *    in flowinfo field when connecting TCP socket,
-     *    or sending UDP packet.
+     * 1. IPv6 on Solaris/Mac OS:
+     *    Set the TOS OR Traffic Class value to cater for
+     *    IPv6 and IPv4 scenarios.
      * 2. IPv6 on Linux: By default Linux ignores flowinfo
      *    field so enable IPV6_FLOWINFO_SEND so that flowinfo
      *    will be examined. We also set the IPv4 TOS option in this case.
@@ -1329,12 +1315,6 @@
     if (level == IPPROTO_IP && opt == IP_TOS) {
         int *iptos;
 
-#if defined(AF_INET6) && (defined(__solaris__) || defined(MACOSX))
-        if (ipv6_available()) {
-            return 0;
-        }
-#endif
-
 #if defined(AF_INET6) && defined(__linux__)
         if (ipv6_available()) {
             int optval = 1;
@@ -1342,6 +1322,16 @@
                            (void *)&optval, sizeof(optval)) < 0) {
                 return -1;
             }
+           /*
+            * Let's also set the IPV6_TCLASS flag.
+            * Linux appears to allow both IP_TOS and IPV6_TCLASS to be set
+            * This helps in mixed environments where IPv4 and IPv6 sockets
+            * are connecting.
+            */
+           if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS,
+                           arg, len) < 0) {
+                return -1;
+            }
         }
 #endif
 
@@ -1435,7 +1425,7 @@
      * On Linux the receive buffer is used for both socket
      * structures and the packet payload. The implication
      * is that if SO_RCVBUF is too small then small packets
-     * must be discard.
+     * must be discarded.
      */
 #ifdef __linux__
     if (level == SOL_SOCKET && opt == SO_RCVBUF) {
@@ -1619,7 +1609,7 @@
  * NET_WAIT_READ, NET_WAIT_WRITE & NET_WAIT_CONNECT.
  *
  * The function will return when either the socket is ready for one
- * of the specified operation or the timeout expired.
+ * of the specified operations or the timeout expired.
  *
  * It returns the time left from the timeout (possibly 0), or -1 if it expired.
  */
--- a/jdk/test/java/net/SocketOption/OptionsTest.java	Thu Jun 04 10:27:06 2015 +0100
+++ b/jdk/test/java/net/SocketOption/OptionsTest.java	Thu Jun 04 18:16:25 2015 +0100
@@ -23,8 +23,9 @@
 
 /*
  * @test
- * @bug 8036979
+ * @bug 8036979 8072384
  * @run main/othervm -Xcheck:jni OptionsTest
+ * @run main/othervm -Xcheck:jni -Djava.net.preferIPv4Stack=true OptionsTest
  */
 
 import java.net.*;
@@ -59,7 +60,8 @@
 
     static Test[] serverSocketTests = new Test[] {
         Test.create(StandardSocketOptions.SO_RCVBUF, Integer.valueOf(8 * 100)),
-        Test.create(StandardSocketOptions.SO_REUSEADDR, Boolean.FALSE)
+        Test.create(StandardSocketOptions.SO_REUSEADDR, Boolean.FALSE),
+        Test.create(StandardSocketOptions.IP_TOS, Integer.valueOf(100))
     };
 
     static Test[] dgSocketTests = new Test[] {
@@ -193,6 +195,9 @@
                 return Integer.valueOf(socket.getReceiveBufferSize());
             } else if (option.equals(StandardSocketOptions.SO_REUSEADDR)) {
                 return Boolean.valueOf(socket.getReuseAddress());
+            } else if (option.equals(StandardSocketOptions.IP_TOS)) {
+                return Integer.valueOf(jdk.net.Sockets.getOption(
+                    socket, StandardSocketOptions.IP_TOS));
             } else {
                 throw new RuntimeException("unexecpted socket option");
             }