| /* |
| * Copyright (c) 2007, 2012, Oracle and/or its affiliates. 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.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"); |
| closesocket(fd); |
| 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, jboolean exclBind) { |
| 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 = NET_WinBind(fd, (struct sockaddr *)&sa, sa_len, exclBind); |
| |
| 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); |
| /* 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; |
| } |
| |
| if (packetBufferLen > MAX_BUFFER_LEN) { |
| fullPacket = (char *)malloc(packetBufferLen); |
| if (!fullPacket) { |
| JNU_ThrowOutOfMemoryError(env, "Native 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, "Native 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; |
| } |