| /* |
| * Copyright (c) 1997, 2017, 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 <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| |
| #if defined(__solaris__) |
| #include <sys/filio.h> |
| #endif |
| |
| #include "net_util.h" |
| |
| #include "java_net_PlainDatagramSocketImpl.h" |
| #include "java_net_InetAddress.h" |
| #include "java_net_NetworkInterface.h" |
| #include "java_net_SocketOptions.h" |
| |
| #ifdef __linux__ |
| #define IPV6_MULTICAST_IF 17 |
| #ifndef SO_BSDCOMPAT |
| #define SO_BSDCOMPAT 14 |
| #endif |
| /** |
| * IP_MULTICAST_ALL has been supported since kernel version 2.6.31 |
| * but we may be building on a machine that is older than that. |
| */ |
| #ifndef IP_MULTICAST_ALL |
| #define IP_MULTICAST_ALL 49 |
| #endif |
| #endif // __linux__ |
| |
| #ifdef __solaris__ |
| #ifndef BSD_COMP |
| #define BSD_COMP |
| #endif |
| #endif |
| |
| #ifndef IPTOS_TOS_MASK |
| #define IPTOS_TOS_MASK 0x1e |
| #endif |
| #ifndef IPTOS_PREC_MASK |
| #define IPTOS_PREC_MASK 0xe0 |
| #endif |
| |
| /************************************************************************ |
| * PlainDatagramSocketImpl |
| */ |
| |
| static jfieldID IO_fd_fdID; |
| |
| static jfieldID pdsi_fdID; |
| static jfieldID pdsi_timeoutID; |
| static jfieldID pdsi_trafficClassID; |
| static jfieldID pdsi_localPortID; |
| static jfieldID pdsi_connected; |
| static jfieldID pdsi_connectedAddress; |
| static jfieldID pdsi_connectedPort; |
| |
| extern void setDefaultScopeID(JNIEnv *env, struct sockaddr *him); |
| extern int getDefaultScopeID(JNIEnv *env); |
| |
| |
| /* |
| * Returns a java.lang.Integer based on 'i' |
| */ |
| static jobject createInteger(JNIEnv *env, int i) { |
| static jclass i_class; |
| static jmethodID i_ctrID; |
| |
| if (i_class == NULL) { |
| jclass c = (*env)->FindClass(env, "java/lang/Integer"); |
| CHECK_NULL_RETURN(c, NULL); |
| i_ctrID = (*env)->GetMethodID(env, c, "<init>", "(I)V"); |
| CHECK_NULL_RETURN(i_ctrID, NULL); |
| i_class = (*env)->NewGlobalRef(env, c); |
| CHECK_NULL_RETURN(i_class, NULL); |
| } |
| |
| return (*env)->NewObject(env, i_class, i_ctrID, i); |
| } |
| |
| /* |
| * Returns a java.lang.Boolean based on 'b' |
| */ |
| static jobject createBoolean(JNIEnv *env, int b) { |
| static jclass b_class; |
| static jmethodID b_ctrID; |
| |
| if (b_class == NULL) { |
| jclass c = (*env)->FindClass(env, "java/lang/Boolean"); |
| CHECK_NULL_RETURN(c, NULL); |
| b_ctrID = (*env)->GetMethodID(env, c, "<init>", "(Z)V"); |
| CHECK_NULL_RETURN(b_ctrID, NULL); |
| b_class = (*env)->NewGlobalRef(env, c); |
| CHECK_NULL_RETURN(b_class, NULL); |
| } |
| |
| return (*env)->NewObject(env, b_class, b_ctrID, (jboolean)(b != 0)); |
| } |
| |
| /* |
| * Returns the fd for a PlainDatagramSocketImpl or -1 |
| * if closed. |
| */ |
| static int getFD(JNIEnv *env, jobject this) { |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| if (fdObj == NULL) { |
| return -1; |
| } |
| return (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: init |
| * Signature: ()V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_init(JNIEnv *env, jclass cls) { |
| |
| pdsi_fdID = (*env)->GetFieldID(env, cls, "fd", |
| "Ljava/io/FileDescriptor;"); |
| CHECK_NULL(pdsi_fdID); |
| pdsi_timeoutID = (*env)->GetFieldID(env, cls, "timeout", "I"); |
| CHECK_NULL(pdsi_timeoutID); |
| pdsi_trafficClassID = (*env)->GetFieldID(env, cls, "trafficClass", "I"); |
| CHECK_NULL(pdsi_trafficClassID); |
| pdsi_localPortID = (*env)->GetFieldID(env, cls, "localPort", "I"); |
| CHECK_NULL(pdsi_localPortID); |
| pdsi_connected = (*env)->GetFieldID(env, cls, "connected", "Z"); |
| CHECK_NULL(pdsi_connected); |
| pdsi_connectedAddress = (*env)->GetFieldID(env, cls, "connectedAddress", |
| "Ljava/net/InetAddress;"); |
| CHECK_NULL(pdsi_connectedAddress); |
| pdsi_connectedPort = (*env)->GetFieldID(env, cls, "connectedPort", "I"); |
| CHECK_NULL(pdsi_connectedPort); |
| |
| IO_fd_fdID = NET_GetFileDescriptorID(env); |
| CHECK_NULL(IO_fd_fdID); |
| |
| initInetAddressIDs(env); |
| JNU_CHECK_EXCEPTION(env); |
| Java_java_net_NetworkInterface_init(env, 0); |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: bind |
| * Signature: (ILjava/net/InetAddress;)V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_bind0(JNIEnv *env, jobject this, |
| jint localport, jobject iaObj) { |
| /* fdObj is the FileDescriptor field on this */ |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| /* fd is an int field on fdObj */ |
| int fd; |
| int len = 0; |
| SOCKETADDRESS sa; |
| socklen_t slen = sizeof(SOCKETADDRESS); |
| |
| if (IS_NULL(fdObj)) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket closed"); |
| return; |
| } else { |
| fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| } |
| |
| if (IS_NULL(iaObj)) { |
| JNU_ThrowNullPointerException(env, "iaObj is null."); |
| return; |
| } |
| |
| /* bind */ |
| if (NET_InetAddressToSockaddr(env, iaObj, localport, &sa, &len, |
| JNI_TRUE) != 0) { |
| return; |
| } |
| setDefaultScopeID(env, &sa.sa); |
| |
| if (NET_Bind(fd, &sa, len) < 0) { |
| if (errno == EADDRINUSE || errno == EADDRNOTAVAIL || |
| errno == EPERM || errno == EACCES) { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "BindException", |
| "Bind failed"); |
| } else { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Bind failed"); |
| } |
| return; |
| } |
| |
| /* initialize the local port */ |
| if (localport == 0) { |
| /* Now that we're a connected socket, let's extract the port number |
| * that the system chose for us and store it in the Socket object. |
| */ |
| if (getsockname(fd, &sa.sa, &slen) == -1) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error getting socket name"); |
| return; |
| } |
| |
| localport = NET_GetPortFromSockaddr(&sa); |
| |
| (*env)->SetIntField(env, this, pdsi_localPortID, localport); |
| } else { |
| (*env)->SetIntField(env, this, pdsi_localPortID, localport); |
| } |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: connect0 |
| * Signature: (Ljava/net/InetAddress;I)V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_connect0(JNIEnv *env, jobject this, |
| jobject address, jint port) { |
| /* The object's field */ |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| /* The fdObj'fd */ |
| jint fd; |
| /* The packetAddress address, family and port */ |
| SOCKETADDRESS rmtaddr; |
| int len = 0; |
| |
| if (IS_NULL(fdObj)) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket closed"); |
| return; |
| } |
| fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| |
| if (IS_NULL(address)) { |
| JNU_ThrowNullPointerException(env, "address"); |
| return; |
| } |
| |
| if (NET_InetAddressToSockaddr(env, address, port, &rmtaddr, &len, |
| JNI_TRUE) != 0) { |
| return; |
| } |
| |
| setDefaultScopeID(env, &rmtaddr.sa); |
| |
| if (NET_Connect(fd, &rmtaddr.sa, len) == -1) { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException", |
| "Connect failed"); |
| } |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: disconnect0 |
| * Signature: ()V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_disconnect0(JNIEnv *env, jobject this, jint family) { |
| /* The object's field */ |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| /* The fdObj'fd */ |
| jint fd; |
| |
| #if defined(__linux__) || defined(_ALLBSD_SOURCE) |
| SOCKETADDRESS addr; |
| socklen_t len; |
| #if defined(__linux__) |
| int localPort = 0; |
| #endif |
| #endif |
| |
| if (IS_NULL(fdObj)) { |
| return; |
| } |
| fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| |
| #if defined(__linux__) || defined(_ALLBSD_SOURCE) |
| memset(&addr, 0, sizeof(addr)); |
| if (ipv6_available()) { |
| addr.sa6.sin6_family = AF_UNSPEC; |
| len = sizeof(struct sockaddr_in6); |
| } else { |
| addr.sa4.sin_family = AF_UNSPEC; |
| len = sizeof(struct sockaddr_in); |
| } |
| NET_Connect(fd, &addr.sa, len); |
| |
| #if defined(__linux__) |
| if (getsockname(fd, &addr.sa, &len) == -1) |
| return; |
| |
| localPort = NET_GetPortFromSockaddr(&addr); |
| if (localPort == 0) { |
| localPort = (*env)->GetIntField(env, this, pdsi_localPortID); |
| if (addr.sa.sa_family == AF_INET6) { |
| addr.sa6.sin6_port = htons(localPort); |
| } else { |
| addr.sa4.sin_port = htons(localPort); |
| } |
| |
| NET_Bind(fd, &addr, len); |
| } |
| |
| #endif |
| #else |
| NET_Connect(fd, 0, 0); |
| #endif |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: send |
| * Signature: (Ljava/net/DatagramPacket;)V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_send(JNIEnv *env, jobject this, |
| jobject packet) { |
| |
| char BUF[MAX_BUFFER_LEN]; |
| char *fullPacket = NULL; |
| int ret, mallocedPacket = JNI_FALSE; |
| /* The object's field */ |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| jint trafficClass = (*env)->GetIntField(env, this, pdsi_trafficClassID); |
| |
| jbyteArray packetBuffer; |
| jobject packetAddress; |
| jint packetBufferOffset, packetBufferLen, packetPort; |
| jboolean connected; |
| |
| /* The fdObj'fd */ |
| jint fd; |
| |
| SOCKETADDRESS rmtaddr; |
| struct sockaddr *rmtaddrP = 0; |
| int len = 0; |
| |
| if (IS_NULL(fdObj)) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket closed"); |
| return; |
| } |
| fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| |
| if (IS_NULL(packet)) { |
| JNU_ThrowNullPointerException(env, "packet"); |
| return; |
| } |
| |
| connected = (*env)->GetBooleanField(env, this, pdsi_connected); |
| |
| packetBuffer = (*env)->GetObjectField(env, packet, dp_bufID); |
| packetAddress = (*env)->GetObjectField(env, packet, dp_addressID); |
| if (IS_NULL(packetBuffer) || IS_NULL(packetAddress)) { |
| JNU_ThrowNullPointerException(env, "null buffer || null address"); |
| return; |
| } |
| |
| packetBufferOffset = (*env)->GetIntField(env, packet, dp_offsetID); |
| packetBufferLen = (*env)->GetIntField(env, packet, dp_lengthID); |
| |
| // arg to NET_Sendto() null, if connected |
| if (!connected) { |
| packetPort = (*env)->GetIntField(env, packet, dp_portID); |
| if (NET_InetAddressToSockaddr(env, packetAddress, packetPort, &rmtaddr, |
| &len, JNI_TRUE) != 0) { |
| return; |
| } |
| rmtaddrP = &rmtaddr.sa; |
| } |
| setDefaultScopeID(env, &rmtaddr.sa); |
| |
| if (packetBufferLen > MAX_BUFFER_LEN) { |
| /* When JNI-ifying the JDK's IO routines, we turned |
| * reads and writes of byte arrays of size greater |
| * than 2048 bytes into several operations of size 2048. |
| * This saves a malloc()/memcpy()/free() for big |
| * buffers. This is OK for file IO and TCP, but that |
| * strategy violates the semantics of a datagram protocol. |
| * (one big send) != (several smaller sends). So here |
| * we *must* allocate the buffer. Note it needn't be bigger |
| * than 65,536 (0xFFFF), the max size of an IP packet. |
| * Anything bigger should be truncated anyway. |
| * |
| * We may want to use a smarter allocation scheme at some |
| * point. |
| */ |
| if (packetBufferLen > MAX_PACKET_LEN) { |
| packetBufferLen = MAX_PACKET_LEN; |
| } |
| fullPacket = (char *)malloc(packetBufferLen); |
| |
| if (!fullPacket) { |
| JNU_ThrowOutOfMemoryError(env, "Send buffer native heap allocation failed"); |
| return; |
| } else { |
| mallocedPacket = JNI_TRUE; |
| } |
| } else { |
| fullPacket = &(BUF[0]); |
| } |
| |
| (*env)->GetByteArrayRegion(env, packetBuffer, packetBufferOffset, packetBufferLen, |
| (jbyte *)fullPacket); |
| if (trafficClass != 0 && ipv6_available()) { |
| NET_SetTrafficClass(&rmtaddr, trafficClass); |
| } |
| |
| /* |
| * Send the datagram. |
| * |
| * If we are connected it's possible that sendto will return |
| * ECONNREFUSED indicating that an ICMP port unreachable has |
| * received. |
| */ |
| ret = NET_SendTo(fd, fullPacket, packetBufferLen, 0, rmtaddrP, len); |
| |
| if (ret < 0) { |
| if (errno == ECONNREFUSED) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException", |
| "ICMP Port Unreachable"); |
| } else { |
| JNU_ThrowIOExceptionWithLastError(env, "sendto failed"); |
| } |
| } |
| |
| if (mallocedPacket) { |
| free(fullPacket); |
| } |
| return; |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: peek |
| * Signature: (Ljava/net/InetAddress;)I |
| */ |
| JNIEXPORT jint JNICALL |
| Java_java_net_PlainDatagramSocketImpl_peek(JNIEnv *env, jobject this, |
| jobject addressObj) { |
| |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| jint timeout = (*env)->GetIntField(env, this, pdsi_timeoutID); |
| jint fd; |
| ssize_t n; |
| SOCKETADDRESS rmtaddr; |
| socklen_t slen = sizeof(SOCKETADDRESS); |
| char buf[1]; |
| jint family; |
| jobject iaObj; |
| int port; |
| if (IS_NULL(fdObj)) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| return -1; |
| } else { |
| fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| } |
| if (IS_NULL(addressObj)) { |
| JNU_ThrowNullPointerException(env, "Null address in peek()"); |
| return -1; |
| } |
| if (timeout) { |
| int ret = NET_Timeout(env, fd, timeout, JVM_NanoTime(env, 0)); |
| if (ret == 0) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", |
| "Peek timed out"); |
| return ret; |
| } else if (ret == -1) { |
| if (errno == EBADF) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| } else if (errno == ENOMEM) { |
| JNU_ThrowOutOfMemoryError(env, "NET_Timeout native heap allocation failed"); |
| } else { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Peek failed"); |
| } |
| return ret; |
| } |
| } |
| |
| n = NET_RecvFrom(fd, buf, 1, MSG_PEEK, &rmtaddr.sa, &slen); |
| |
| if (n == -1) { |
| |
| #ifdef __solaris__ |
| if (errno == ECONNREFUSED) { |
| int orig_errno = errno; |
| recv(fd, buf, 1, 0); |
| errno = orig_errno; |
| } |
| #endif |
| if (errno == ECONNREFUSED) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException", |
| "ICMP Port Unreachable"); |
| } else { |
| if (errno == EBADF) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| } else { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Peek failed"); |
| } |
| } |
| return 0; |
| } |
| |
| iaObj = NET_SockaddrToInetAddress(env, &rmtaddr, &port); |
| family = getInetAddress_family(env, iaObj) == java_net_InetAddress_IPv4 ? |
| AF_INET : AF_INET6; |
| if (family == AF_INET) { /* this API can't handle IPV6 addresses */ |
| int address = getInetAddress_addr(env, iaObj); |
| setInetAddress_addr(env, addressObj, address); |
| } |
| return port; |
| } |
| |
| JNIEXPORT jint JNICALL |
| Java_java_net_PlainDatagramSocketImpl_peekData(JNIEnv *env, jobject this, |
| jobject packet) { |
| |
| char BUF[MAX_BUFFER_LEN]; |
| char *fullPacket = NULL; |
| int mallocedPacket = JNI_FALSE; |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| jint timeout = (*env)->GetIntField(env, this, pdsi_timeoutID); |
| jbyteArray packetBuffer; |
| jint packetBufferOffset, packetBufferLen; |
| int fd; |
| int n; |
| SOCKETADDRESS rmtaddr; |
| socklen_t slen = sizeof(SOCKETADDRESS); |
| int port = -1; |
| |
| if (IS_NULL(fdObj)) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket closed"); |
| return -1; |
| } |
| |
| fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| |
| if (IS_NULL(packet)) { |
| JNU_ThrowNullPointerException(env, "packet"); |
| return -1; |
| } |
| |
| packetBuffer = (*env)->GetObjectField(env, packet, dp_bufID); |
| if (IS_NULL(packetBuffer)) { |
| JNU_ThrowNullPointerException(env, "packet buffer"); |
| return -1; |
| } |
| packetBufferOffset = (*env)->GetIntField(env, packet, dp_offsetID); |
| packetBufferLen = (*env)->GetIntField(env, packet, dp_bufLengthID); |
| if (timeout) { |
| int ret = NET_Timeout(env, fd, timeout, JVM_NanoTime(env, 0)); |
| if (ret == 0) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", |
| "Receive timed out"); |
| return -1; |
| } else if (ret == -1) { |
| if (errno == ENOMEM) { |
| JNU_ThrowOutOfMemoryError(env, "NET_Timeout native heap allocation failed"); |
| #ifdef __linux__ |
| } else if (errno == EBADF) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| } else { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Receive failed"); |
| #else |
| } else { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| #endif |
| } |
| return -1; |
| } |
| } |
| |
| if (packetBufferLen > MAX_BUFFER_LEN) { |
| |
| /* When JNI-ifying the JDK's IO routines, we turned |
| * reads and writes of byte arrays of size greater |
| * than 2048 bytes into several operations of size 2048. |
| * This saves a malloc()/memcpy()/free() for big |
| * buffers. This is OK for file IO and TCP, but that |
| * strategy violates the semantics of a datagram protocol. |
| * (one big send) != (several smaller sends). So here |
| * we *must* allocate the buffer. Note it needn't be bigger |
| * than 65,536 (0xFFFF), the max size of an IP packet. |
| * anything bigger is truncated anyway. |
| * |
| * We may want to use a smarter allocation scheme at some |
| * point. |
| */ |
| if (packetBufferLen > MAX_PACKET_LEN) { |
| packetBufferLen = MAX_PACKET_LEN; |
| } |
| fullPacket = (char *)malloc(packetBufferLen); |
| |
| if (!fullPacket) { |
| JNU_ThrowOutOfMemoryError(env, "Peek buffer native heap allocation failed"); |
| return -1; |
| } else { |
| mallocedPacket = JNI_TRUE; |
| } |
| } else { |
| fullPacket = &(BUF[0]); |
| } |
| |
| n = NET_RecvFrom(fd, fullPacket, packetBufferLen, MSG_PEEK, |
| &rmtaddr.sa, &slen); |
| /* truncate the data if the packet's length is too small */ |
| if (n > packetBufferLen) { |
| n = packetBufferLen; |
| } |
| if (n == -1) { |
| |
| #ifdef __solaris__ |
| if (errno == ECONNREFUSED) { |
| int orig_errno = errno; |
| (void) recv(fd, fullPacket, 1, 0); |
| errno = orig_errno; |
| } |
| #endif |
| (*env)->SetIntField(env, packet, dp_offsetID, 0); |
| (*env)->SetIntField(env, packet, dp_lengthID, 0); |
| if (errno == ECONNREFUSED) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException", |
| "ICMP Port Unreachable"); |
| } else { |
| if (errno == EBADF) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| } else { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Receive failed"); |
| } |
| } |
| } else { |
| /* |
| * success - fill in received address... |
| * |
| * REMIND: Fill in an int on the packet, and create inetadd |
| * object in Java, as a performance improvement. Also |
| * construct the inetadd object lazily. |
| */ |
| |
| 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, packet, dp_addressID); |
| if (packetAddress != NULL) { |
| if (!NET_SockaddrEqualsInetAddress(env, &rmtaddr, packetAddress)) { |
| /* force a new InetAddress to be created */ |
| packetAddress = NULL; |
| } |
| } |
| if (packetAddress == NULL) { |
| packetAddress = NET_SockaddrToInetAddress(env, &rmtaddr, &port); |
| /* stuff the new Inetaddress in the packet */ |
| (*env)->SetObjectField(env, packet, dp_addressID, packetAddress); |
| } else { |
| /* only get the new port number */ |
| port = NET_GetPortFromSockaddr(&rmtaddr); |
| } |
| /* and fill in the data, remote address/port and such */ |
| (*env)->SetByteArrayRegion(env, packetBuffer, packetBufferOffset, n, |
| (jbyte *)fullPacket); |
| (*env)->SetIntField(env, packet, dp_portID, port); |
| (*env)->SetIntField(env, packet, dp_lengthID, n); |
| } |
| |
| if (mallocedPacket) { |
| free(fullPacket); |
| } |
| return port; |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: receive |
| * Signature: (Ljava/net/DatagramPacket;)V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_receive0(JNIEnv *env, jobject this, |
| jobject packet) { |
| |
| char BUF[MAX_BUFFER_LEN]; |
| char *fullPacket = NULL; |
| int mallocedPacket = JNI_FALSE; |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| jint timeout = (*env)->GetIntField(env, this, pdsi_timeoutID); |
| |
| jbyteArray packetBuffer; |
| jint packetBufferOffset, packetBufferLen; |
| |
| int fd; |
| |
| int n; |
| SOCKETADDRESS rmtaddr; |
| socklen_t slen = sizeof(SOCKETADDRESS); |
| jboolean retry; |
| #ifdef __linux__ |
| jboolean connected = JNI_FALSE; |
| jobject connectedAddress = NULL; |
| jint connectedPort = 0; |
| jlong prevTime = 0; |
| #endif |
| |
| if (IS_NULL(fdObj)) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket closed"); |
| return; |
| } |
| |
| fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| |
| if (IS_NULL(packet)) { |
| JNU_ThrowNullPointerException(env, "packet"); |
| return; |
| } |
| |
| packetBuffer = (*env)->GetObjectField(env, packet, dp_bufID); |
| if (IS_NULL(packetBuffer)) { |
| JNU_ThrowNullPointerException(env, "packet buffer"); |
| return; |
| } |
| packetBufferOffset = (*env)->GetIntField(env, packet, dp_offsetID); |
| packetBufferLen = (*env)->GetIntField(env, packet, dp_bufLengthID); |
| |
| if (packetBufferLen > MAX_BUFFER_LEN) { |
| |
| /* When JNI-ifying the JDK's IO routines, we turned |
| * reads and writes of byte arrays of size greater |
| * than 2048 bytes into several operations of size 2048. |
| * This saves a malloc()/memcpy()/free() for big |
| * buffers. This is OK for file IO and TCP, but that |
| * strategy violates the semantics of a datagram protocol. |
| * (one big send) != (several smaller sends). So here |
| * we *must* allocate the buffer. Note it needn't be bigger |
| * than 65,536 (0xFFFF) the max size of an IP packet, |
| * anything bigger is truncated anyway. |
| * |
| * We may want to use a smarter allocation scheme at some |
| * point. |
| */ |
| if (packetBufferLen > MAX_PACKET_LEN) { |
| packetBufferLen = MAX_PACKET_LEN; |
| } |
| fullPacket = (char *)malloc(packetBufferLen); |
| |
| if (!fullPacket) { |
| JNU_ThrowOutOfMemoryError(env, "Receive buffer native heap allocation failed"); |
| return; |
| } else { |
| mallocedPacket = JNI_TRUE; |
| } |
| } else { |
| fullPacket = &(BUF[0]); |
| } |
| |
| do { |
| retry = JNI_FALSE; |
| |
| if (timeout) { |
| int ret = NET_Timeout(env, fd, timeout, JVM_NanoTime(env, 0)); |
| if (ret <= 0) { |
| if (ret == 0) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", |
| "Receive timed out"); |
| } else if (ret == -1) { |
| if (errno == ENOMEM) { |
| JNU_ThrowOutOfMemoryError(env, "NET_Timeout native heap allocation failed"); |
| #ifdef __linux__ |
| } else if (errno == EBADF) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| } else { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Receive failed"); |
| #else |
| } else { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| #endif |
| } |
| } |
| |
| if (mallocedPacket) { |
| free(fullPacket); |
| } |
| |
| return; |
| } |
| } |
| |
| n = NET_RecvFrom(fd, fullPacket, packetBufferLen, 0, |
| &rmtaddr.sa, &slen); |
| /* truncate the data if the packet's length is too small */ |
| if (n > packetBufferLen) { |
| n = packetBufferLen; |
| } |
| if (n == -1) { |
| (*env)->SetIntField(env, packet, dp_offsetID, 0); |
| (*env)->SetIntField(env, packet, dp_lengthID, 0); |
| if (errno == ECONNREFUSED) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException", |
| "ICMP Port Unreachable"); |
| } else { |
| if (errno == EBADF) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| } else { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Receive failed"); |
| } |
| } |
| } else { |
| int port; |
| jobject packetAddress; |
| |
| /* |
| * success - fill in received address... |
| * |
| * REMIND: Fill in an int on the packet, and create inetadd |
| * object in Java, as a performance improvement. Also |
| * construct the inetadd object lazily. |
| */ |
| |
| /* |
| * 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, packet, dp_addressID); |
| if (packetAddress != NULL) { |
| if (!NET_SockaddrEqualsInetAddress(env, &rmtaddr, |
| packetAddress)) { |
| /* force a new InetAddress to be created */ |
| packetAddress = NULL; |
| } |
| } |
| if (packetAddress == NULL) { |
| packetAddress = NET_SockaddrToInetAddress(env, &rmtaddr, &port); |
| /* stuff the new Inetaddress in the packet */ |
| (*env)->SetObjectField(env, packet, dp_addressID, packetAddress); |
| } else { |
| /* only get the new port number */ |
| port = NET_GetPortFromSockaddr(&rmtaddr); |
| } |
| /* and fill in the data, remote address/port and such */ |
| (*env)->SetByteArrayRegion(env, packetBuffer, packetBufferOffset, n, |
| (jbyte *)fullPacket); |
| (*env)->SetIntField(env, packet, dp_portID, port); |
| (*env)->SetIntField(env, packet, dp_lengthID, n); |
| } |
| |
| } while (retry); |
| |
| if (mallocedPacket) { |
| free(fullPacket); |
| } |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: datagramSocketCreate |
| * Signature: ()V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_datagramSocketCreate(JNIEnv *env, |
| jobject this) { |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| int arg, fd, t = 1; |
| char tmpbuf[1024]; |
| int domain = ipv6_available() ? AF_INET6 : AF_INET; |
| |
| if (IS_NULL(fdObj)) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket closed"); |
| return; |
| } |
| |
| if ((fd = socket(domain, SOCK_DGRAM, 0)) == -1) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error creating socket"); |
| return; |
| } |
| |
| /* Disable IPV6_V6ONLY to ensure dual-socket support */ |
| if (domain == AF_INET6) { |
| arg = 0; |
| if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg, |
| sizeof(int)) < 0) { |
| NET_ThrowNew(env, errno, "cannot set IPPROTO_IPV6"); |
| close(fd); |
| return; |
| } |
| } |
| |
| #ifdef __APPLE__ |
| arg = 65507; |
| if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, |
| (char *)&arg, sizeof(arg)) < 0) { |
| getErrorString(errno, tmpbuf, sizeof(tmpbuf)); |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", tmpbuf); |
| close(fd); |
| return; |
| } |
| if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, |
| (char *)&arg, sizeof(arg)) < 0) { |
| getErrorString(errno, tmpbuf, sizeof(tmpbuf)); |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", tmpbuf); |
| close(fd); |
| return; |
| } |
| #endif /* __APPLE__ */ |
| |
| if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char*) &t, sizeof (int)) < 0) { |
| getErrorString(errno, tmpbuf, sizeof(tmpbuf)); |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", tmpbuf); |
| close(fd); |
| return; |
| } |
| |
| #if defined(__linux__) |
| arg = 0; |
| int level = (domain == AF_INET6) ? IPPROTO_IPV6 : IPPROTO_IP; |
| if ((setsockopt(fd, level, IP_MULTICAST_ALL, (char*)&arg, sizeof(arg)) < 0) && |
| (errno != ENOPROTOOPT)) |
| { |
| getErrorString(errno, tmpbuf, sizeof(tmpbuf)); |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", tmpbuf); |
| close(fd); |
| return; |
| } |
| #endif |
| |
| #if defined (__linux__) |
| /* |
| * On Linux for IPv6 sockets we must set the hop limit |
| * to 1 to be compatible with default TTL of 1 for IPv4 sockets. |
| */ |
| if (domain == AF_INET6) { |
| int ttl = 1; |
| if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *) &ttl, |
| sizeof (ttl)) < 0) { |
| getErrorString(errno, tmpbuf, sizeof(tmpbuf)); |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", tmpbuf); |
| close(fd); |
| return; |
| } |
| } |
| #endif /* __linux__ */ |
| |
| (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd); |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: datagramSocketClose |
| * Signature: ()V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_datagramSocketClose(JNIEnv *env, |
| jobject this) { |
| /* |
| * REMIND: PUT A LOCK AROUND THIS CODE |
| */ |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| int fd; |
| |
| if (IS_NULL(fdObj)) { |
| return; |
| } |
| fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| if (fd == -1) { |
| return; |
| } |
| (*env)->SetIntField(env, fdObj, IO_fd_fdID, -1); |
| NET_SocketClose(fd); |
| } |
| |
| |
| /* |
| * Set outgoing multicast interface designated by a NetworkInterface. |
| * Throw exception if failed. |
| */ |
| static void mcast_set_if_by_if_v4(JNIEnv *env, jobject this, int fd, jobject value) { |
| static jfieldID ni_addrsID; |
| struct in_addr in; |
| jobjectArray addrArray; |
| jsize len; |
| jobject addr; |
| int i; |
| |
| if (ni_addrsID == NULL ) { |
| jclass c = (*env)->FindClass(env, "java/net/NetworkInterface"); |
| CHECK_NULL(c); |
| ni_addrsID = (*env)->GetFieldID(env, c, "addrs", |
| "[Ljava/net/InetAddress;"); |
| CHECK_NULL(ni_addrsID); |
| } |
| |
| addrArray = (*env)->GetObjectField(env, value, ni_addrsID); |
| len = (*env)->GetArrayLength(env, addrArray); |
| |
| /* |
| * Check that there is at least one address bound to this |
| * interface. |
| */ |
| if (len < 1) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "bad argument for IP_MULTICAST_IF2: No IP addresses bound to interface"); |
| return; |
| } |
| |
| /* |
| * We need an ipv4 address here |
| */ |
| in.s_addr = 0; |
| for (i = 0; i < len; i++) { |
| addr = (*env)->GetObjectArrayElement(env, addrArray, i); |
| if (getInetAddress_family(env, addr) == java_net_InetAddress_IPv4) { |
| in.s_addr = htonl(getInetAddress_addr(env, addr)); |
| break; |
| } |
| } |
| |
| if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, |
| (const char *)&in, sizeof(in)) < 0) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error setting socket option"); |
| } |
| } |
| |
| /* |
| * Set outgoing multicast interface designated by a NetworkInterface. |
| * Throw exception if failed. |
| */ |
| static void mcast_set_if_by_if_v6(JNIEnv *env, jobject this, int fd, jobject value) { |
| static jfieldID ni_indexID; |
| int index; |
| |
| if (ni_indexID == NULL) { |
| jclass c = (*env)->FindClass(env, "java/net/NetworkInterface"); |
| CHECK_NULL(c); |
| ni_indexID = (*env)->GetFieldID(env, c, "index", "I"); |
| CHECK_NULL(ni_indexID); |
| } |
| index = (*env)->GetIntField(env, value, ni_indexID); |
| |
| if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, |
| (const char*)&index, sizeof(index)) < 0) { |
| if (errno == EINVAL && index > 0) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "IPV6_MULTICAST_IF failed (interface has IPv4 " |
| "address only?)"); |
| } else { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error setting socket option"); |
| } |
| return; |
| } |
| } |
| |
| /* |
| * Set outgoing multicast interface designated by an InetAddress. |
| * Throw exception if failed. |
| */ |
| static void mcast_set_if_by_addr_v4(JNIEnv *env, jobject this, int fd, jobject value) { |
| struct in_addr in; |
| |
| in.s_addr = htonl( getInetAddress_addr(env, value) ); |
| |
| if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, |
| (const char*)&in, sizeof(in)) < 0) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error setting socket option"); |
| } |
| } |
| |
| /* |
| * Set outgoing multicast interface designated by an InetAddress. |
| * Throw exception if failed. |
| */ |
| static void mcast_set_if_by_addr_v6(JNIEnv *env, jobject this, int fd, jobject value) { |
| static jclass ni_class; |
| if (ni_class == NULL) { |
| jclass c = (*env)->FindClass(env, "java/net/NetworkInterface"); |
| CHECK_NULL(c); |
| ni_class = (*env)->NewGlobalRef(env, c); |
| CHECK_NULL(ni_class); |
| } |
| |
| value = Java_java_net_NetworkInterface_getByInetAddress0(env, ni_class, value); |
| if (value == NULL) { |
| if (!(*env)->ExceptionOccurred(env)) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "bad argument for IP_MULTICAST_IF" |
| ": address not bound to any interface"); |
| } |
| return; |
| } |
| |
| mcast_set_if_by_if_v6(env, this, fd, value); |
| } |
| |
| /* |
| * Sets the multicast interface. |
| * |
| * SocketOptions.IP_MULTICAST_IF :- |
| * value is a InetAddress |
| * IPv4: set outgoing multicast interface using |
| * IPPROTO_IP/IP_MULTICAST_IF |
| * IPv6: Get the index of the interface to which the |
| * InetAddress is bound |
| * Set outgoing multicast interface using |
| * IPPROTO_IPV6/IPV6_MULTICAST_IF |
| * |
| * SockOptions.IF_MULTICAST_IF2 :- |
| * value is a NetworkInterface |
| * IPv4: Obtain IP address bound to network interface |
| * (NetworkInterface.addres[0]) |
| * set outgoing multicast interface using |
| * IPPROTO_IP/IP_MULTICAST_IF |
| * IPv6: Obtain NetworkInterface.index |
| * Set outgoing multicast interface using |
| * IPPROTO_IPV6/IPV6_MULTICAST_IF |
| * |
| */ |
| static void setMulticastInterface(JNIEnv *env, jobject this, int fd, |
| jint opt, jobject value) |
| { |
| if (opt == java_net_SocketOptions_IP_MULTICAST_IF) { |
| /* |
| * value is an InetAddress. |
| */ |
| #ifdef __linux__ |
| mcast_set_if_by_addr_v4(env, this, fd, value); |
| if (ipv6_available()) { |
| if ((*env)->ExceptionCheck(env)){ |
| (*env)->ExceptionClear(env); |
| } |
| mcast_set_if_by_addr_v6(env, this, fd, value); |
| } |
| #else /* __linux__ not defined */ |
| if (ipv6_available()) { |
| mcast_set_if_by_addr_v6(env, this, fd, value); |
| } else { |
| mcast_set_if_by_addr_v4(env, this, fd, value); |
| } |
| #endif /* __linux__ */ |
| } |
| |
| if (opt == java_net_SocketOptions_IP_MULTICAST_IF2) { |
| /* |
| * value is a NetworkInterface. |
| */ |
| #ifdef __linux__ |
| mcast_set_if_by_if_v4(env, this, fd, value); |
| if (ipv6_available()) { |
| if ((*env)->ExceptionCheck(env)){ |
| (*env)->ExceptionClear(env); |
| } |
| mcast_set_if_by_if_v6(env, this, fd, value); |
| } |
| #else /* __linux__ not defined */ |
| if (ipv6_available()) { |
| mcast_set_if_by_if_v6(env, this, fd, value); |
| } else { |
| mcast_set_if_by_if_v4(env, this, fd, value); |
| } |
| #endif /* __linux__ */ |
| } |
| } |
| |
| /* |
| * Enable/disable local loopback of multicast datagrams. |
| */ |
| static void mcast_set_loop_v4(JNIEnv *env, jobject this, int fd, jobject value) { |
| jclass cls; |
| jfieldID fid; |
| jboolean on; |
| char loopback; |
| |
| cls = (*env)->FindClass(env, "java/lang/Boolean"); |
| CHECK_NULL(cls); |
| fid = (*env)->GetFieldID(env, cls, "value", "Z"); |
| CHECK_NULL(fid); |
| |
| on = (*env)->GetBooleanField(env, value, fid); |
| loopback = (!on ? 1 : 0); |
| |
| if (NET_SetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, |
| (const void *)&loopback, sizeof(char)) < 0) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error setting socket option"); |
| return; |
| } |
| } |
| |
| /* |
| * Enable/disable local loopback of multicast datagrams. |
| */ |
| static void mcast_set_loop_v6(JNIEnv *env, jobject this, int fd, jobject value) { |
| jclass cls; |
| jfieldID fid; |
| jboolean on; |
| int loopback; |
| |
| cls = (*env)->FindClass(env, "java/lang/Boolean"); |
| CHECK_NULL(cls); |
| fid = (*env)->GetFieldID(env, cls, "value", "Z"); |
| CHECK_NULL(fid); |
| |
| on = (*env)->GetBooleanField(env, value, fid); |
| loopback = (!on ? 1 : 0); |
| |
| if (NET_SetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, |
| (const void *)&loopback, sizeof(int)) < 0) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error setting socket option"); |
| return; |
| } |
| |
| } |
| |
| /* |
| * Sets the multicast loopback mode. |
| */ |
| static void setMulticastLoopbackMode(JNIEnv *env, jobject this, int fd, |
| jint opt, jobject value) { |
| #ifdef __linux__ |
| mcast_set_loop_v4(env, this, fd, value); |
| if (ipv6_available()) { |
| if ((*env)->ExceptionCheck(env)){ |
| (*env)->ExceptionClear(env); |
| } |
| mcast_set_loop_v6(env, this, fd, value); |
| } |
| #else /* __linux__ not defined */ |
| if (ipv6_available()) { |
| mcast_set_loop_v6(env, this, fd, value); |
| } else { |
| mcast_set_loop_v4(env, this, fd, value); |
| } |
| #endif /* __linux__ */ |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: socketSetOption0 |
| * Signature: (ILjava/lang/Object;)V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_socketSetOption0 |
| (JNIEnv *env, jobject this, jint opt, jobject value) |
| { |
| int fd; |
| int level, optname, optlen; |
| int optval; |
| optlen = sizeof(int); |
| |
| /* |
| * Check that socket hasn't been closed |
| */ |
| fd = getFD(env, this); |
| if (fd < 0) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket closed"); |
| return; |
| } |
| |
| /* |
| * Check argument has been provided |
| */ |
| if (IS_NULL(value)) { |
| JNU_ThrowNullPointerException(env, "value argument"); |
| return; |
| } |
| |
| /* |
| * Setting the multicast interface handled separately |
| */ |
| if (opt == java_net_SocketOptions_IP_MULTICAST_IF || |
| opt == java_net_SocketOptions_IP_MULTICAST_IF2) { |
| |
| setMulticastInterface(env, this, fd, opt, value); |
| return; |
| } |
| |
| /* |
| * Setting the multicast loopback mode handled separately |
| */ |
| if (opt == java_net_SocketOptions_IP_MULTICAST_LOOP) { |
| setMulticastLoopbackMode(env, this, fd, opt, value); |
| return; |
| } |
| |
| /* |
| * Map the Java level socket option to the platform specific |
| * level and option name. |
| */ |
| if (NET_MapSocketOption(opt, &level, &optname)) { |
| JNU_ThrowByName(env, "java/net/SocketException", "Invalid option"); |
| return; |
| } |
| |
| switch (opt) { |
| case java_net_SocketOptions_SO_SNDBUF : |
| case java_net_SocketOptions_SO_RCVBUF : |
| case java_net_SocketOptions_IP_TOS : |
| { |
| jclass cls; |
| jfieldID fid; |
| |
| cls = (*env)->FindClass(env, "java/lang/Integer"); |
| CHECK_NULL(cls); |
| fid = (*env)->GetFieldID(env, cls, "value", "I"); |
| CHECK_NULL(fid); |
| |
| optval = (*env)->GetIntField(env, value, fid); |
| break; |
| } |
| |
| case java_net_SocketOptions_SO_REUSEADDR: |
| case java_net_SocketOptions_SO_REUSEPORT: |
| case java_net_SocketOptions_SO_BROADCAST: |
| { |
| jclass cls; |
| jfieldID fid; |
| jboolean on; |
| |
| cls = (*env)->FindClass(env, "java/lang/Boolean"); |
| CHECK_NULL(cls); |
| fid = (*env)->GetFieldID(env, cls, "value", "Z"); |
| CHECK_NULL(fid); |
| |
| on = (*env)->GetBooleanField(env, value, fid); |
| |
| /* SO_REUSEADDR or SO_BROADCAST */ |
| optval = (on ? 1 : 0); |
| |
| break; |
| } |
| |
| default : |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket option not supported by PlainDatagramSocketImp"); |
| return; |
| |
| } |
| |
| if (NET_SetSockOpt(fd, level, optname, (const void *)&optval, optlen) < 0) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error setting socket option"); |
| return; |
| } |
| } |
| |
| |
| /* |
| * Return the multicast interface: |
| * |
| * SocketOptions.IP_MULTICAST_IF |
| * IPv4: Query IPPROTO_IP/IP_MULTICAST_IF |
| * Create InetAddress |
| * IP_MULTICAST_IF returns struct ip_mreqn on 2.2 |
| * kernel but struct in_addr on 2.4 kernel |
| * IPv6: Query IPPROTO_IPV6 / IPV6_MULTICAST_IF |
| * If index == 0 return InetAddress representing |
| * anyLocalAddress. |
| * If index > 0 query NetworkInterface by index |
| * and returns addrs[0] |
| * |
| * SocketOptions.IP_MULTICAST_IF2 |
| * IPv4: Query IPPROTO_IP/IP_MULTICAST_IF |
| * Query NetworkInterface by IP address and |
| * return the NetworkInterface that the address |
| * is bound too. |
| * IPv6: Query IPPROTO_IPV6 / IPV6_MULTICAST_IF |
| * (except Linux .2 kernel) |
| * Query NetworkInterface by index and |
| * return NetworkInterface. |
| */ |
| jobject getMulticastInterface(JNIEnv *env, jobject this, int fd, jint opt) { |
| jboolean isIPV4 = JNI_TRUE; |
| |
| if (ipv6_available()) { |
| isIPV4 = JNI_FALSE; |
| } |
| |
| /* |
| * IPv4 implementation |
| */ |
| if (isIPV4) { |
| static jclass inet4_class; |
| static jmethodID inet4_ctrID; |
| |
| static jclass ni_class; |
| static jmethodID ni_ctrID; |
| static jfieldID ni_indexID; |
| static jfieldID ni_addrsID; |
| static jfieldID ni_nameID; |
| |
| jobjectArray addrArray; |
| jobject addr; |
| jobject ni; |
| jobject ni_name; |
| |
| struct in_addr in; |
| struct in_addr *inP = ∈ |
| socklen_t len = sizeof(struct in_addr); |
| |
| if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, |
| (char *)inP, &len) < 0) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error getting socket option"); |
| return NULL; |
| } |
| |
| /* |
| * Construct and populate an Inet4Address |
| */ |
| if (inet4_class == NULL) { |
| jclass c = (*env)->FindClass(env, "java/net/Inet4Address"); |
| CHECK_NULL_RETURN(c, NULL); |
| inet4_ctrID = (*env)->GetMethodID(env, c, "<init>", "()V"); |
| CHECK_NULL_RETURN(inet4_ctrID, NULL); |
| inet4_class = (*env)->NewGlobalRef(env, c); |
| CHECK_NULL_RETURN(inet4_class, NULL); |
| } |
| addr = (*env)->NewObject(env, inet4_class, inet4_ctrID, 0); |
| CHECK_NULL_RETURN(addr, NULL); |
| |
| setInetAddress_addr(env, addr, ntohl(in.s_addr)); |
| |
| /* |
| * For IP_MULTICAST_IF return InetAddress |
| */ |
| if (opt == java_net_SocketOptions_IP_MULTICAST_IF) { |
| return addr; |
| } |
| |
| /* |
| * For IP_MULTICAST_IF2 we get the NetworkInterface for |
| * this address and return it |
| */ |
| if (ni_class == NULL) { |
| jclass c = (*env)->FindClass(env, "java/net/NetworkInterface"); |
| CHECK_NULL_RETURN(c, NULL); |
| ni_ctrID = (*env)->GetMethodID(env, c, "<init>", "()V"); |
| CHECK_NULL_RETURN(ni_ctrID, NULL); |
| ni_indexID = (*env)->GetFieldID(env, c, "index", "I"); |
| CHECK_NULL_RETURN(ni_indexID, NULL); |
| ni_addrsID = (*env)->GetFieldID(env, c, "addrs", |
| "[Ljava/net/InetAddress;"); |
| CHECK_NULL_RETURN(ni_addrsID, NULL); |
| ni_nameID = (*env)->GetFieldID(env, c,"name", "Ljava/lang/String;"); |
| CHECK_NULL_RETURN(ni_nameID, NULL); |
| ni_class = (*env)->NewGlobalRef(env, c); |
| CHECK_NULL_RETURN(ni_class, NULL); |
| } |
| ni = Java_java_net_NetworkInterface_getByInetAddress0(env, ni_class, addr); |
| if (ni) { |
| return ni; |
| } |
| |
| /* |
| * The address doesn't appear to be bound at any known |
| * NetworkInterface. Therefore we construct a NetworkInterface |
| * with this address. |
| */ |
| ni = (*env)->NewObject(env, ni_class, ni_ctrID, 0); |
| CHECK_NULL_RETURN(ni, NULL); |
| |
| (*env)->SetIntField(env, ni, ni_indexID, -1); |
| addrArray = (*env)->NewObjectArray(env, 1, inet4_class, NULL); |
| CHECK_NULL_RETURN(addrArray, NULL); |
| (*env)->SetObjectArrayElement(env, addrArray, 0, addr); |
| (*env)->SetObjectField(env, ni, ni_addrsID, addrArray); |
| ni_name = (*env)->NewStringUTF(env, ""); |
| if (ni_name != NULL) { |
| (*env)->SetObjectField(env, ni, ni_nameID, ni_name); |
| } |
| return ni; |
| } |
| |
| |
| /* |
| * IPv6 implementation |
| */ |
| if ((opt == java_net_SocketOptions_IP_MULTICAST_IF) || |
| (opt == java_net_SocketOptions_IP_MULTICAST_IF2)) { |
| |
| static jclass ni_class; |
| static jmethodID ni_ctrID; |
| static jfieldID ni_indexID; |
| static jfieldID ni_addrsID; |
| static jclass ia_class; |
| static jfieldID ni_nameID; |
| static jmethodID ia_anyLocalAddressID; |
| |
| int index = 0; |
| socklen_t len = sizeof(index); |
| |
| jobjectArray addrArray; |
| jobject addr; |
| jobject ni; |
| jobject ni_name; |
| |
| if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, |
| (char*)&index, &len) < 0) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error getting socket option"); |
| return NULL; |
| } |
| |
| if (ni_class == NULL) { |
| jclass c = (*env)->FindClass(env, "java/net/NetworkInterface"); |
| CHECK_NULL_RETURN(c, NULL); |
| ni_ctrID = (*env)->GetMethodID(env, c, "<init>", "()V"); |
| CHECK_NULL_RETURN(ni_ctrID, NULL); |
| ni_indexID = (*env)->GetFieldID(env, c, "index", "I"); |
| CHECK_NULL_RETURN(ni_indexID, NULL); |
| ni_addrsID = (*env)->GetFieldID(env, c, "addrs", |
| "[Ljava/net/InetAddress;"); |
| CHECK_NULL_RETURN(ni_addrsID, NULL); |
| |
| ia_class = (*env)->FindClass(env, "java/net/InetAddress"); |
| CHECK_NULL_RETURN(ia_class, NULL); |
| ia_class = (*env)->NewGlobalRef(env, ia_class); |
| CHECK_NULL_RETURN(ia_class, NULL); |
| ia_anyLocalAddressID = (*env)->GetStaticMethodID(env, |
| ia_class, |
| "anyLocalAddress", |
| "()Ljava/net/InetAddress;"); |
| CHECK_NULL_RETURN(ia_anyLocalAddressID, NULL); |
| ni_nameID = (*env)->GetFieldID(env, c,"name", "Ljava/lang/String;"); |
| CHECK_NULL_RETURN(ni_nameID, NULL); |
| ni_class = (*env)->NewGlobalRef(env, c); |
| CHECK_NULL_RETURN(ni_class, NULL); |
| } |
| |
| /* |
| * If multicast to a specific interface then return the |
| * interface (for IF2) or the any address on that interface |
| * (for IF). |
| */ |
| if (index > 0) { |
| ni = Java_java_net_NetworkInterface_getByIndex0(env, ni_class, |
| index); |
| if (ni == NULL) { |
| char errmsg[255]; |
| sprintf(errmsg, |
| "IPV6_MULTICAST_IF returned index to unrecognized interface: %d", |
| index); |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", errmsg); |
| return NULL; |
| } |
| |
| /* |
| * For IP_MULTICAST_IF2 return the NetworkInterface |
| */ |
| if (opt == java_net_SocketOptions_IP_MULTICAST_IF2) { |
| return ni; |
| } |
| |
| /* |
| * For IP_MULTICAST_IF return addrs[0] |
| */ |
| addrArray = (*env)->GetObjectField(env, ni, ni_addrsID); |
| if ((*env)->GetArrayLength(env, addrArray) < 1) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "IPV6_MULTICAST_IF returned interface without IP bindings"); |
| return NULL; |
| } |
| |
| addr = (*env)->GetObjectArrayElement(env, addrArray, 0); |
| return addr; |
| } |
| |
| /* |
| * Multicast to any address - return anyLocalAddress |
| * or a NetworkInterface with addrs[0] set to anyLocalAddress |
| */ |
| |
| addr = (*env)->CallStaticObjectMethod(env, ia_class, ia_anyLocalAddressID, |
| NULL); |
| if (opt == java_net_SocketOptions_IP_MULTICAST_IF) { |
| return addr; |
| } |
| |
| ni = (*env)->NewObject(env, ni_class, ni_ctrID, 0); |
| CHECK_NULL_RETURN(ni, NULL); |
| (*env)->SetIntField(env, ni, ni_indexID, -1); |
| addrArray = (*env)->NewObjectArray(env, 1, ia_class, NULL); |
| CHECK_NULL_RETURN(addrArray, NULL); |
| (*env)->SetObjectArrayElement(env, addrArray, 0, addr); |
| (*env)->SetObjectField(env, ni, ni_addrsID, addrArray); |
| ni_name = (*env)->NewStringUTF(env, ""); |
| if (ni_name != NULL) { |
| (*env)->SetObjectField(env, ni, ni_nameID, ni_name); |
| } |
| return ni; |
| } |
| return NULL; |
| } |
| |
| |
| |
| /* |
| * Returns relevant info as a jint. |
| * |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: socketGetOption |
| * Signature: (I)Ljava/lang/Object; |
| */ |
| JNIEXPORT jobject JNICALL |
| Java_java_net_PlainDatagramSocketImpl_socketGetOption |
| (JNIEnv *env, jobject this, jint opt) |
| { |
| int fd; |
| int level, optname, optlen; |
| union { |
| int i; |
| char c; |
| } optval; |
| |
| fd = getFD(env, this); |
| if (fd < 0) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "socket closed"); |
| return NULL; |
| } |
| |
| /* |
| * Handle IP_MULTICAST_IF separately |
| */ |
| if (opt == java_net_SocketOptions_IP_MULTICAST_IF || |
| opt == java_net_SocketOptions_IP_MULTICAST_IF2) { |
| return getMulticastInterface(env, this, fd, opt); |
| |
| } |
| |
| /* |
| * SO_BINDADDR implemented using getsockname |
| */ |
| if (opt == java_net_SocketOptions_SO_BINDADDR) { |
| /* find out local IP address */ |
| SOCKETADDRESS sa; |
| socklen_t len = sizeof(SOCKETADDRESS); |
| int port; |
| jobject iaObj; |
| |
| if (getsockname(fd, &sa.sa, &len) == -1) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error getting socket name"); |
| return NULL; |
| } |
| iaObj = NET_SockaddrToInetAddress(env, &sa, &port); |
| |
| return iaObj; |
| } |
| |
| /* |
| * Map the Java level socket option to the platform specific |
| * level and option name. |
| */ |
| if (NET_MapSocketOption(opt, &level, &optname)) { |
| JNU_ThrowByName(env, "java/net/SocketException", "Invalid option"); |
| return NULL; |
| } |
| |
| if (opt == java_net_SocketOptions_IP_MULTICAST_LOOP && |
| level == IPPROTO_IP) { |
| optlen = sizeof(optval.c); |
| } else { |
| optlen = sizeof(optval.i); |
| } |
| |
| if (NET_GetSockOpt(fd, level, optname, (void *)&optval, &optlen) < 0) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error getting socket option"); |
| return NULL; |
| } |
| |
| switch (opt) { |
| case java_net_SocketOptions_IP_MULTICAST_LOOP: |
| /* getLoopbackMode() returns true if IP_MULTICAST_LOOP disabled */ |
| if (level == IPPROTO_IP) { |
| return createBoolean(env, (int)!optval.c); |
| } else { |
| return createBoolean(env, !optval.i); |
| } |
| |
| case java_net_SocketOptions_SO_BROADCAST: |
| case java_net_SocketOptions_SO_REUSEADDR: |
| return createBoolean(env, optval.i); |
| |
| case java_net_SocketOptions_SO_REUSEPORT: |
| return createBoolean(env, optval.i); |
| |
| case java_net_SocketOptions_SO_SNDBUF: |
| case java_net_SocketOptions_SO_RCVBUF: |
| case java_net_SocketOptions_IP_TOS: |
| return createInteger(env, optval.i); |
| |
| } |
| |
| /* should never reach here */ |
| return NULL; |
| } |
| |
| /* |
| * Multicast-related calls |
| */ |
| |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_setTTL(JNIEnv *env, jobject this, |
| jbyte ttl) { |
| jint ittl = ttl; |
| if (ittl < 0) { |
| ittl += 0x100; |
| } |
| Java_java_net_PlainDatagramSocketImpl_setTimeToLive(env, this, ittl); |
| } |
| |
| /* |
| * Set TTL for a socket. Throw exception if failed. |
| */ |
| static void setTTL(JNIEnv *env, int fd, jint ttl) { |
| char ittl = (char)ttl; |
| if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&ittl, |
| sizeof(ittl)) < 0) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error setting socket option"); |
| } |
| } |
| |
| /* |
| * Set hops limit for a socket. Throw exception if failed. |
| */ |
| static void setHopLimit(JNIEnv *env, int fd, jint ttl) { |
| int ittl = (int)ttl; |
| if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, |
| (char*)&ittl, sizeof(ittl)) < 0) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error setting socket option"); |
| } |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: setTTL |
| * Signature: (B)V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_setTimeToLive(JNIEnv *env, jobject this, |
| jint ttl) { |
| |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| int fd; |
| /* it is important to cast this to a char, otherwise setsockopt gets confused */ |
| |
| if (IS_NULL(fdObj)) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket closed"); |
| return; |
| } else { |
| fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| } |
| /* setsockopt to be correct TTL */ |
| #ifdef __linux__ |
| setTTL(env, fd, ttl); |
| JNU_CHECK_EXCEPTION(env); |
| if (ipv6_available()) { |
| setHopLimit(env, fd, ttl); |
| } |
| #else /* __linux__ not defined */ |
| if (ipv6_available()) { |
| setHopLimit(env, fd, ttl); |
| } else { |
| setTTL(env, fd, ttl); |
| } |
| #endif /* __linux__ */ |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: getTTL |
| * Signature: ()B |
| */ |
| JNIEXPORT jbyte JNICALL |
| Java_java_net_PlainDatagramSocketImpl_getTTL(JNIEnv *env, jobject this) { |
| return (jbyte)Java_java_net_PlainDatagramSocketImpl_getTimeToLive(env, this); |
| } |
| |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: getTTL |
| * Signature: ()B |
| */ |
| JNIEXPORT jint JNICALL |
| Java_java_net_PlainDatagramSocketImpl_getTimeToLive(JNIEnv *env, jobject this) { |
| |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| jint fd = -1; |
| |
| if (IS_NULL(fdObj)) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket closed"); |
| return -1; |
| } else { |
| fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| } |
| /* getsockopt of TTL */ |
| if (ipv6_available()) { |
| int ttl = 0; |
| socklen_t len = sizeof(ttl); |
| |
| if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, |
| (char*)&ttl, &len) < 0) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error getting socket option"); |
| return -1; |
| } |
| return (jint)ttl; |
| } else { |
| u_char ttl = 0; |
| socklen_t len = sizeof(ttl); |
| if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, |
| (char*)&ttl, &len) < 0) { |
| JNU_ThrowByNameWithMessageAndLastError |
| (env, JNU_JAVANETPKG "SocketException", "Error getting socket option"); |
| return -1; |
| } |
| return (jint)ttl; |
| } |
| } |
| |
| |
| /* |
| * mcast_join_leave: Join or leave a multicast group. |
| * |
| * For IPv4 sockets use IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP socket option |
| * to join/leave multicast group. |
| * |
| * For IPv6 sockets use IPV6_ADD_MEMBERSHIP/IPV6_DROP_MEMBERSHIP socket option |
| * to join/leave multicast group. If multicast group is an IPv4 address then |
| * an IPv4-mapped address is used. |
| * |
| * On Linux with IPv6 if we wish to join/leave an IPv4 multicast group then |
| * we must use the IPv4 socket options. This is because the IPv6 socket options |
| * don't support IPv4-mapped addresses. This is true as per 2.2.19 and 2.4.7 |
| * kernel releases. In the future it's possible that IP_ADD_MEMBERSHIP |
| * will be updated to return ENOPROTOOPT if uses with an IPv6 socket (Solaris |
| * already does this). Thus to cater for this we first try with the IPv4 |
| * socket options and if they fail we use the IPv6 socket options. This |
| * seems a reasonable failsafe solution. |
| */ |
| static void mcast_join_leave(JNIEnv *env, jobject this, |
| jobject iaObj, jobject niObj, |
| jboolean join) { |
| |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| jint fd; |
| jint ipv6_join_leave; |
| |
| if (IS_NULL(fdObj)) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket closed"); |
| return; |
| } else { |
| fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| } |
| if (IS_NULL(iaObj)) { |
| JNU_ThrowNullPointerException(env, "iaObj"); |
| return; |
| } |
| |
| /* |
| * Determine if this is an IPv4 or IPv6 join/leave. |
| */ |
| ipv6_join_leave = ipv6_available(); |
| |
| #ifdef __linux__ |
| if (getInetAddress_family(env, iaObj) == java_net_InetAddress_IPv4) { |
| ipv6_join_leave = JNI_FALSE; |
| } |
| #endif |
| |
| /* |
| * For IPv4 join use IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP socket option |
| * |
| * On Linux if IPv4 or IPv6 use IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP |
| */ |
| if (!ipv6_join_leave) { |
| #ifdef __linux__ |
| struct ip_mreqn mname; |
| #else |
| struct ip_mreq mname; |
| #endif |
| int mname_len; |
| |
| /* |
| * joinGroup(InetAddress, NetworkInterface) implementation :- |
| * |
| * Linux/IPv6: use ip_mreqn structure populated with multicast |
| * address and interface index. |
| * |
| * IPv4: use ip_mreq structure populated with multicast |
| * address and first address obtained from |
| * NetworkInterface |
| */ |
| if (niObj != NULL) { |
| #if defined(__linux__) |
| if (ipv6_available()) { |
| static jfieldID ni_indexID; |
| |
| if (ni_indexID == NULL) { |
| jclass c = (*env)->FindClass(env, "java/net/NetworkInterface"); |
| CHECK_NULL(c); |
| ni_indexID = (*env)->GetFieldID(env, c, "index", "I"); |
| CHECK_NULL(ni_indexID); |
| } |
| |
| mname.imr_multiaddr.s_addr = htonl(getInetAddress_addr(env, iaObj)); |
| mname.imr_address.s_addr = 0; |
| mname.imr_ifindex = (*env)->GetIntField(env, niObj, ni_indexID); |
| mname_len = sizeof(struct ip_mreqn); |
| } else |
| #endif |
| { |
| jobjectArray addrArray = (*env)->GetObjectField(env, niObj, ni_addrsID); |
| jobject addr; |
| |
| if ((*env)->GetArrayLength(env, addrArray) < 1) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "bad argument for IP_ADD_MEMBERSHIP: " |
| "No IP addresses bound to interface"); |
| return; |
| } |
| addr = (*env)->GetObjectArrayElement(env, addrArray, 0); |
| |
| mname.imr_multiaddr.s_addr = htonl(getInetAddress_addr(env, iaObj)); |
| #ifdef __linux__ |
| mname.imr_address.s_addr = htonl(getInetAddress_addr(env, addr)); |
| mname.imr_ifindex = 0; |
| #else |
| mname.imr_interface.s_addr = htonl(getInetAddress_addr(env, addr)); |
| #endif |
| mname_len = sizeof(struct ip_mreq); |
| } |
| } |
| |
| |
| /* |
| * joinGroup(InetAddress) implementation :- |
| * |
| * Linux/IPv6: use ip_mreqn structure populated with multicast |
| * address and interface index. index obtained |
| * from cached value or IPV6_MULTICAST_IF. |
| * |
| * IPv4: use ip_mreq structure populated with multicast |
| * address and local address obtained from |
| * IP_MULTICAST_IF. On Linux IP_MULTICAST_IF |
| * returns different structure depending on |
| * kernel. |
| */ |
| |
| if (niObj == NULL) { |
| |
| #if defined(__linux__) |
| if (ipv6_available()) { |
| |
| int index; |
| socklen_t len = sizeof(index); |
| |
| if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, |
| (char*)&index, &len) < 0) { |
| NET_ThrowCurrent(env, "getsockopt IPV6_MULTICAST_IF failed"); |
| return; |
| } |
| |
| mname.imr_multiaddr.s_addr = htonl(getInetAddress_addr(env, iaObj)); |
| mname.imr_address.s_addr = 0 ; |
| mname.imr_ifindex = index; |
| mname_len = sizeof(struct ip_mreqn); |
| } else |
| #endif |
| { |
| struct in_addr in; |
| struct in_addr *inP = ∈ |
| socklen_t len = sizeof(struct in_addr); |
| |
| if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (char *)inP, &len) < 0) { |
| NET_ThrowCurrent(env, "getsockopt IP_MULTICAST_IF failed"); |
| return; |
| } |
| |
| #ifdef __linux__ |
| mname.imr_address.s_addr = in.s_addr; |
| mname.imr_ifindex = 0; |
| #else |
| mname.imr_interface.s_addr = in.s_addr; |
| #endif |
| mname.imr_multiaddr.s_addr = htonl(getInetAddress_addr(env, iaObj)); |
| mname_len = sizeof(struct ip_mreq); |
| } |
| } |
| |
| |
| /* |
| * Join the multicast group. |
| */ |
| if (setsockopt(fd, IPPROTO_IP, (join ? IP_ADD_MEMBERSHIP:IP_DROP_MEMBERSHIP), |
| (char *) &mname, mname_len) < 0) { |
| |
| /* |
| * If IP_ADD_MEMBERSHIP returns ENOPROTOOPT on Linux and we've got |
| * IPv6 enabled then it's possible that the kernel has been fixed |
| * so we switch to IPV6_ADD_MEMBERSHIP socket option. |
| * As of 2.4.7 kernel IPV6_ADD_MEMBERSHIP can't handle IPv4-mapped |
| * addresses so we have to use IP_ADD_MEMBERSHIP for IPv4 multicast |
| * groups. However if the socket is an IPv6 socket then setsockopt |
| * should return ENOPROTOOPT. We assume this will be fixed in Linux |
| * at some stage. |
| */ |
| #if defined(__linux__) |
| if (errno == ENOPROTOOPT) { |
| if (ipv6_available()) { |
| ipv6_join_leave = JNI_TRUE; |
| errno = 0; |
| } else { |
| errno = ENOPROTOOPT; /* errno can be changed by ipv6_available */ |
| } |
| } |
| #endif |
| if (errno) { |
| if (join) { |
| NET_ThrowCurrent(env, "setsockopt IP_ADD_MEMBERSHIP failed"); |
| } else { |
| if (errno == ENOENT) |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Not a member of the multicast group"); |
| else |
| NET_ThrowCurrent(env, "setsockopt IP_DROP_MEMBERSHIP failed"); |
| } |
| return; |
| } |
| } |
| |
| /* |
| * If we haven't switched to IPv6 socket option then we're done. |
| */ |
| if (!ipv6_join_leave) { |
| return; |
| } |
| } |
| |
| |
| /* |
| * IPv6 join. If it's an IPv4 multicast group then we use an IPv4-mapped |
| * address. |
| */ |
| { |
| struct ipv6_mreq mname6; |
| jbyteArray ipaddress; |
| jbyte caddr[16]; |
| jint family; |
| jint address; |
| family = getInetAddress_family(env, iaObj) == java_net_InetAddress_IPv4 ? |
| AF_INET : AF_INET6; |
| if (family == AF_INET) { /* will convert to IPv4-mapped address */ |
| memset((char *) caddr, 0, 16); |
| address = getInetAddress_addr(env, iaObj); |
| |
| caddr[10] = 0xff; |
| caddr[11] = 0xff; |
| |
| caddr[12] = ((address >> 24) & 0xff); |
| caddr[13] = ((address >> 16) & 0xff); |
| caddr[14] = ((address >> 8) & 0xff); |
| caddr[15] = (address & 0xff); |
| } else { |
| getInet6Address_ipaddress(env, iaObj, (char*)caddr); |
| } |
| |
| memcpy((void *)&(mname6.ipv6mr_multiaddr), caddr, sizeof(struct in6_addr)); |
| if (IS_NULL(niObj)) { |
| int index; |
| socklen_t len = sizeof(index); |
| |
| if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, |
| (char*)&index, &len) < 0) { |
| NET_ThrowCurrent(env, "getsockopt IPV6_MULTICAST_IF failed"); |
| return; |
| } |
| |
| #ifdef __linux__ |
| /* |
| * On 2.4.8+ if we join a group with the interface set to 0 |
| * then the kernel records the interface it decides. This causes |
| * subsequent leave groups to fail as there is no match. Thus we |
| * pick the interface if there is a matching route. |
| */ |
| if (index == 0) { |
| int rt_index = getDefaultIPv6Interface(&(mname6.ipv6mr_multiaddr)); |
| if (rt_index > 0) { |
| index = rt_index; |
| } |
| } |
| #endif |
| #ifdef MACOSX |
| if (family == AF_INET6 && index == 0) { |
| index = getDefaultScopeID(env); |
| } |
| #endif |
| mname6.ipv6mr_interface = index; |
| } else { |
| jint idx = (*env)->GetIntField(env, niObj, ni_indexID); |
| mname6.ipv6mr_interface = idx; |
| } |
| |
| #if defined(_ALLBSD_SOURCE) |
| #define ADD_MEMBERSHIP IPV6_JOIN_GROUP |
| #define DRP_MEMBERSHIP IPV6_LEAVE_GROUP |
| #define S_ADD_MEMBERSHIP "IPV6_JOIN_GROUP" |
| #define S_DRP_MEMBERSHIP "IPV6_LEAVE_GROUP" |
| #else |
| #define ADD_MEMBERSHIP IPV6_ADD_MEMBERSHIP |
| #define DRP_MEMBERSHIP IPV6_DROP_MEMBERSHIP |
| #define S_ADD_MEMBERSHIP "IPV6_ADD_MEMBERSHIP" |
| #define S_DRP_MEMBERSHIP "IPV6_DROP_MEMBERSHIP" |
| #endif |
| |
| /* Join the multicast group */ |
| if (setsockopt(fd, IPPROTO_IPV6, (join ? ADD_MEMBERSHIP : DRP_MEMBERSHIP), |
| (char *) &mname6, sizeof (mname6)) < 0) { |
| |
| if (join) { |
| NET_ThrowCurrent(env, "setsockopt " S_ADD_MEMBERSHIP " failed"); |
| } else { |
| if (errno == ENOENT) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Not a member of the multicast group"); |
| } else { |
| NET_ThrowCurrent(env, "setsockopt " S_DRP_MEMBERSHIP " failed"); |
| } |
| } |
| } |
| } |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: join |
| * Signature: (Ljava/net/InetAddress;)V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_join(JNIEnv *env, jobject this, |
| jobject iaObj, jobject niObj) |
| { |
| mcast_join_leave(env, this, iaObj, niObj, JNI_TRUE); |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: leave |
| * Signature: (Ljava/net/InetAddress;)V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_leave(JNIEnv *env, jobject this, |
| jobject iaObj, jobject niObj) |
| { |
| mcast_join_leave(env, this, iaObj, niObj, JNI_FALSE); |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: dataAvailable |
| * Signature: ()I |
| */ |
| JNIEXPORT jint JNICALL |
| Java_java_net_PlainDatagramSocketImpl_dataAvailable(JNIEnv *env, jobject this) |
| { |
| int fd, retval; |
| |
| jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); |
| |
| if (IS_NULL(fdObj)) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket closed"); |
| return -1; |
| } |
| fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| |
| if (ioctl(fd, FIONREAD, &retval) < 0) { |
| return -1; |
| } |
| return retval; |
| } |