jdk/src/windows/native/java/net/DualStackPlainDatagramSocketImpl.c
changeset 2 90ce3da70b43
child 5506 202f599c92aa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/windows/native/java/net/DualStackPlainDatagramSocketImpl.c	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,496 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+#include <windows.h>
+#include <winsock2.h>
+#include "jni.h"
+#include "net_util.h"
+#include "java_net_DualStackPlainDatagramSocketImpl.h"
+
+/*
+ * This function "purges" all outstanding ICMP port unreachable packets
+ * outstanding on a socket and returns JNI_TRUE if any ICMP messages
+ * have been purged. The rational for purging is to emulate normal BSD
+ * behaviour whereby receiving a "connection reset" status resets the
+ * socket.
+ */
+static jboolean purgeOutstandingICMP(JNIEnv *env, jint fd)
+{
+    jboolean got_icmp = JNI_FALSE;
+    char buf[1];
+    fd_set tbl;
+    struct timeval t = { 0, 0 };
+    struct sockaddr_in rmtaddr;
+    int addrlen = sizeof(rmtaddr);
+
+    /*
+     * Peek at the queue to see if there is an ICMP port unreachable. If there
+     * is then receive it.
+     */
+    FD_ZERO(&tbl);
+    FD_SET(fd, &tbl);
+    while(1) {
+        if (select(/*ignored*/fd+1, &tbl, 0, 0, &t) <= 0) {
+            break;
+        }
+        if (recvfrom(fd, buf, 1, MSG_PEEK,
+                         (struct sockaddr *)&rmtaddr, &addrlen) != JVM_IO_ERR) {
+            break;
+        }
+        if (WSAGetLastError() != WSAECONNRESET) {
+            /* some other error - we don't care here */
+            break;
+        }
+
+        recvfrom(fd, buf, 1, 0,  (struct sockaddr *)&rmtaddr, &addrlen);
+        got_icmp = JNI_TRUE;
+    }
+
+    return got_icmp;
+}
+
+/*
+ * Class:     java_net_DualStackPlainDatagramSocketImpl
+ * Method:    socketCreate
+ * Signature: (Z)I
+ */
+JNIEXPORT jint JNICALL Java_java_net_DualStackPlainDatagramSocketImpl_socketCreate
+  (JNIEnv *env, jclass clazz, jboolean v6Only /*unused*/) {
+    int fd, rv, opt=0, t=TRUE;
+    DWORD x1, x2; /* ignored result codes */
+
+    fd = (int) socket(AF_INET6, SOCK_DGRAM, 0);
+    if (fd == INVALID_SOCKET) {
+        NET_ThrowNew(env, WSAGetLastError(), "Socket creation failed");
+        return -1;
+    }
+
+    rv = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &opt, sizeof(opt));
+    if (rv == SOCKET_ERROR) {
+        NET_ThrowNew(env, WSAGetLastError(), "Socket creation failed");
+        return -1;
+    }
+
+    SetHandleInformation((HANDLE)(UINT_PTR)fd, HANDLE_FLAG_INHERIT, FALSE);
+    NET_SetSockOpt(fd, SOL_SOCKET, SO_BROADCAST, (char*)&t, sizeof(BOOL));
+
+    /* SIO_UDP_CONNRESET fixes a "bug" introduced in Windows 2000, which
+     * returns connection reset errors on unconnected UDP sockets (as well
+     * as connected sockets). The solution is to only enable this feature
+     * when the socket is connected.
+     */
+    t = FALSE;
+    WSAIoctl(fd ,SIO_UDP_CONNRESET ,&t ,sizeof(t) ,&x1 ,sizeof(x1) ,&x2 ,0 ,0);
+
+    return fd;
+}
+
+/*
+ * Class:     java_net_DualStackPlainDatagramSocketImpl
+ * Method:    socketBind
+ * Signature: (ILjava/net/InetAddress;I)V
+ */
+JNIEXPORT void JNICALL Java_java_net_DualStackPlainDatagramSocketImpl_socketBind
+  (JNIEnv *env, jclass clazz, jint fd, jobject iaObj, jint port) {
+    SOCKETADDRESS sa;
+    int rv;
+    int sa_len = sizeof(sa);
+
+    if (NET_InetAddressToSockaddr(env, iaObj, port, (struct sockaddr *)&sa,
+                                 &sa_len, JNI_TRUE) != 0) {
+        return;
+    }
+
+    rv = bind(fd, (struct sockaddr *)&sa, sa_len);
+
+    if (rv == SOCKET_ERROR) {
+        if (WSAGetLastError() == WSAEACCES) {
+            WSASetLastError(WSAEADDRINUSE);
+        }
+        NET_ThrowNew(env, WSAGetLastError(), "Cannot bind");
+    }
+}
+
+/*
+ * Class:     java_net_DualStackPlainDatagramSocketImpl
+ * Method:    socketConnect
+ * Signature: (ILjava/net/InetAddress;I)V
+ */
+JNIEXPORT void JNICALL Java_java_net_DualStackPlainDatagramSocketImpl_socketConnect
+  (JNIEnv *env, jclass clazz, jint fd, jobject iaObj, jint port) {
+    SOCKETADDRESS sa;
+    int rv;
+    int sa_len = sizeof(sa);
+    DWORD x1, x2; /* ignored result codes */
+    int t = TRUE;
+
+    if (NET_InetAddressToSockaddr(env, iaObj, port, (struct sockaddr *)&sa,
+                                   &sa_len, JNI_TRUE) != 0) {
+        return;
+    }
+
+    rv = connect(fd, (struct sockaddr *)&sa, sa_len);
+    if (rv == SOCKET_ERROR) {
+        NET_ThrowNew(env, WSAGetLastError(), "connect");
+        return;
+    }
+
+    /* see comment in socketCreate */
+    WSAIoctl(fd, SIO_UDP_CONNRESET, &t, sizeof(t), &x1, sizeof(x1), &x2, 0, 0);
+}
+
+/*
+ * Class:     java_net_DualStackPlainDatagramSocketImpl
+ * Method:    socketDisconnect
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_java_net_DualStackPlainDatagramSocketImpl_socketDisconnect
+  (JNIEnv *env, jclass clazz, jint fd ) {
+    SOCKETADDRESS sa;
+    int sa_len = sizeof(sa);
+    DWORD x1, x2; /* ignored result codes */
+    int t = FALSE;
+
+    memset(&sa, 0, sa_len);
+    connect(fd, (struct sockaddr *)&sa, sa_len);
+
+    /* see comment in socketCreate */
+    WSAIoctl(fd, SIO_UDP_CONNRESET, &t, sizeof(t), &x1, sizeof(x1), &x2, 0, 0);
+}
+
+/*
+ * Class:     java_net_DualStackPlainDatagramSocketImpl
+ * Method:    socketClose
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_java_net_DualStackPlainDatagramSocketImpl_socketClose
+  (JNIEnv *env, jclass clazz , jint fd) {
+    NET_SocketClose(fd);
+}
+
+
+/*
+ * Class:     java_net_DualStackPlainDatagramSocketImpl
+ * Method:    socketLocalPort
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_java_net_DualStackPlainDatagramSocketImpl_socketLocalPort
+  (JNIEnv *env, jclass clazz, jint fd) {
+    SOCKETADDRESS sa;
+    int len = sizeof(sa);
+
+    if (getsockname(fd, (struct sockaddr *)&sa, &len) == SOCKET_ERROR) {
+        NET_ThrowNew(env, WSAGetLastError(), "JVM_GetSockName");
+        return -1;
+    }
+    return (int) ntohs((u_short)GET_PORT(&sa));
+}
+
+/*
+ * Class:     java_net_DualStackPlainDatagramSocketImpl
+ * Method:    socketLocalAddress
+ * Signature: (I)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_java_net_DualStackPlainDatagramSocketImpl_socketLocalAddress
+  (JNIEnv *env , jclass clazz, jint fd) {
+    SOCKETADDRESS sa;
+    int len = sizeof(sa);
+    jobject iaObj;
+    int port;
+
+    if (getsockname(fd, (struct sockaddr *)&sa, &len) == SOCKET_ERROR) {
+        NET_ThrowNew(env, WSAGetLastError(), "Error getting socket name");
+        return NULL;
+    }
+
+    iaObj = NET_SockaddrToInetAddress(env, (struct sockaddr *)&sa, &port);
+    return iaObj;
+}
+
+/*
+ * Class:     java_net_DualStackPlainDatagramSocketImpl
+ * Method:    socketReceiveOrPeekData
+ * Signature: (ILjava/net/DatagramPacket;IZZ)I
+ */
+JNIEXPORT jint JNICALL Java_java_net_DualStackPlainDatagramSocketImpl_socketReceiveOrPeekData
+  (JNIEnv *env, jclass clazz, jint fd, jobject dpObj,
+   jint timeout, jboolean connected, jboolean peek) {
+    SOCKETADDRESS sa;
+    int sa_len = sizeof(sa);
+    int port, rv, flags=0;
+    char BUF[MAX_BUFFER_LEN];
+    char *fullPacket;
+    BOOL retry;
+    jlong prevTime = 0;
+
+    jint packetBufferOffset, packetBufferLen;
+    jbyteArray packetBuffer;
+
+    /* if we are only peeking. Called from peekData */
+    if (peek) {
+        flags = MSG_PEEK;
+    }
+
+    packetBuffer = (*env)->GetObjectField(env, dpObj, dp_bufID);
+    packetBufferOffset = (*env)->GetIntField(env, dpObj, dp_offsetID);
+    packetBufferLen = (*env)->GetIntField(env, dpObj, dp_bufLengthID);
+
+    if (packetBufferLen > MAX_BUFFER_LEN) {
+        /* Note: the buffer needn't be greater than 65,536 (0xFFFF)
+         * the max size of an IP packet. Anything bigger is truncated anyway.
+         */
+        if (packetBufferLen > MAX_PACKET_LEN) {
+            packetBufferLen = MAX_PACKET_LEN;
+        }
+        fullPacket = (char *)malloc(packetBufferLen);
+        if (!fullPacket) {
+            JNU_ThrowOutOfMemoryError(env, "heap allocation failed");
+            return -1;
+        }
+    } else {
+        fullPacket = &(BUF[0]);
+    }
+
+    do {
+        retry = FALSE;
+
+        if (timeout) {
+            if (prevTime == 0) {
+                prevTime = JVM_CurrentTimeMillis(env, 0);
+            }
+            rv = NET_Timeout(fd, timeout);
+            if (rv <= 0) {
+                if (rv == 0) {
+                    JNU_ThrowByName(env,JNU_JAVANETPKG "SocketTimeoutException",
+                                    "Receive timed out");
+                } else if (rv == JVM_IO_ERR) {
+                    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                                    "Socket closed");
+                } else if (rv == JVM_IO_INTR) {
+                    JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+                                    "operation interrupted");
+                }
+                if (packetBufferLen > MAX_BUFFER_LEN) {
+                    free(fullPacket);
+                }
+                return -1;
+            }
+        }
+
+        /* receive the packet */
+        rv = recvfrom(fd, fullPacket, packetBufferLen, flags,
+                    (struct sockaddr *)&sa, &sa_len);
+
+        if (rv == SOCKET_ERROR && (WSAGetLastError() == WSAECONNRESET)) {
+            /* An icmp port unreachable - we must receive this as Windows
+             * does not reset the state of the socket until this has been
+             * received.
+             */
+            purgeOutstandingICMP(env, fd);
+
+            if (connected) {
+                JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+                                "ICMP Port Unreachable");
+                if (packetBufferLen > MAX_BUFFER_LEN)
+                    free(fullPacket);
+                return -1;
+            } else if (timeout) {
+                /* Adjust timeout */
+                jlong newTime = JVM_CurrentTimeMillis(env, 0);
+                timeout -= (jint)(newTime - prevTime);
+                if (timeout <= 0) {
+                    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
+                                    "Receive timed out");
+                    if (packetBufferLen > MAX_BUFFER_LEN)
+                        free(fullPacket);
+                    return -1;
+                }
+                prevTime = newTime;
+            }
+            retry = TRUE;
+        }
+    } while (retry);
+
+    port = (int) ntohs ((u_short) GET_PORT((SOCKETADDRESS *)&sa));
+
+    /* truncate the data if the packet's length is too small */
+    if (rv > packetBufferLen) {
+        rv = packetBufferLen;
+    }
+    if (rv < 0) {
+        if (WSAGetLastError() == WSAEMSGSIZE) {
+            /* it is because the buffer is too small. It's UDP, it's
+             * unreliable, it's all good. discard the rest of the
+             * data..
+             */
+            rv = packetBufferLen;
+        } else {
+            /* failure */
+            (*env)->SetIntField(env, dpObj, dp_lengthID, 0);
+        }
+    }
+
+    if (rv == -1) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "socket closed");
+    } else if (rv == -2) {
+        JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+                        "operation interrupted");
+    } else if (rv < 0) {
+        NET_ThrowCurrent(env, "Datagram receive failed");
+    } else {
+        jobject packetAddress;
+        /*
+         * Check if there is an InetAddress already associated with this
+         * packet. If so, we check if it is the same source address. We
+         * can't update any existing InetAddress because it is immutable
+         */
+        packetAddress = (*env)->GetObjectField(env, dpObj, dp_addressID);
+        if (packetAddress != NULL) {
+            if (!NET_SockaddrEqualsInetAddress(env, (struct sockaddr *)&sa,
+                                               packetAddress)) {
+                /* force a new InetAddress to be created */
+                packetAddress = NULL;
+            }
+        }
+        if (packetAddress == NULL) {
+            packetAddress = NET_SockaddrToInetAddress(env, (struct sockaddr *)&sa,
+                                                      &port);
+            /* stuff the new Inetaddress into the packet */
+            (*env)->SetObjectField(env, dpObj, dp_addressID, packetAddress);
+        }
+
+        /* populate the packet */
+        (*env)->SetByteArrayRegion(env, packetBuffer, packetBufferOffset, rv,
+                                   (jbyte *)fullPacket);
+        (*env)->SetIntField(env, dpObj, dp_portID, port);
+        (*env)->SetIntField(env, dpObj, dp_lengthID, rv);
+    }
+
+    if (packetBufferLen > MAX_BUFFER_LEN) {
+        free(fullPacket);
+    }
+    return port;
+}
+
+/*
+ * Class:     java_net_DualStackPlainDatagramSocketImpl
+ * Method:    socketSend
+ * Signature: (I[BIILjava/net/InetAddress;IZ)V
+ */
+JNIEXPORT void JNICALL Java_java_net_DualStackPlainDatagramSocketImpl_socketSend
+  (JNIEnv *env, jclass clazz, jint fd, jbyteArray data, jint offset, jint length,
+     jobject iaObj, jint port, jboolean connected) {
+    SOCKETADDRESS sa;
+    int sa_len = sizeof(sa);
+    SOCKETADDRESS *sap = &sa;
+    char BUF[MAX_BUFFER_LEN];
+    char *fullPacket;
+    int rv;
+
+    if (connected) {
+        sap = 0; /* arg to JVM_Sendto () null in this case */
+        sa_len = 0;
+    } else {
+        if (NET_InetAddressToSockaddr(env, iaObj, port, (struct sockaddr *)&sa,
+                                       &sa_len, JNI_TRUE) != 0) {
+            return;
+        }
+    }
+
+    if (length > MAX_BUFFER_LEN) {
+        /* Note: the buffer needn't be greater than 65,536 (0xFFFF)
+         * the max size of an IP packet. Anything bigger is truncated anyway.
+         */
+        if (length > MAX_PACKET_LEN) {
+            length = MAX_PACKET_LEN;
+        }
+        fullPacket = (char *)malloc(length);
+        if (!fullPacket) {
+            JNU_ThrowOutOfMemoryError(env, "heap allocation failed");
+            return;
+        }
+    } else {
+        fullPacket = &(BUF[0]);
+    }
+
+    (*env)->GetByteArrayRegion(env, data, offset, length,
+                               (jbyte *)fullPacket);
+    rv = sendto(fd, fullPacket, length, 0, (struct sockaddr *)sap, sa_len);
+    if (rv == SOCKET_ERROR) {
+        if (rv == JVM_IO_ERR) {
+            NET_ThrowNew(env, WSAGetLastError(), "Datagram send failed");
+        } else if (rv == JVM_IO_INTR) {
+            JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+                            "operation interrupted");
+        }
+    }
+
+    if (length > MAX_BUFFER_LEN) {
+        free(fullPacket);
+    }
+}
+
+/*
+ * Class:     java_net_DualStackPlainDatagramSocketImpl
+ * Method:    socketSetIntOption
+ * Signature: (III)V
+ */
+JNIEXPORT void JNICALL Java_java_net_DualStackPlainDatagramSocketImpl_socketSetIntOption
+  (JNIEnv *env, jclass clazz, jint fd , jint cmd, jint value) {
+    int level, opt;
+
+    if (NET_MapSocketOption(cmd, &level, &opt) < 0) {
+        JNU_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                                     "Invalid option");
+        return;
+    }
+
+    if (NET_SetSockOpt(fd, level, opt, (char *)&value, sizeof(value)) < 0) {
+        NET_ThrowNew(env, WSAGetLastError(), "setsockopt");
+    }
+}
+
+/*
+ * Class:     java_net_DualStackPlainDatagramSocketImpl
+ * Method:    socketGetIntOption
+ * Signature: (II)I
+ */
+JNIEXPORT jint JNICALL Java_java_net_DualStackPlainDatagramSocketImpl_socketGetIntOption
+  (JNIEnv *env, jclass clazz, jint fd, jint cmd) {
+    int level, opt, result=0;
+    int result_len = sizeof(result);
+
+    if (NET_MapSocketOption(cmd, &level, &opt) < 0) {
+        JNU_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                                     "Invalid option");
+        return -1;
+    }
+
+    if (NET_GetSockOpt(fd, level, opt, (void *)&result, &result_len) < 0) {
+        NET_ThrowNew(env, WSAGetLastError(), "getsockopt");
+        return -1;
+    }
+
+    return result;
+}