| /* |
| * Copyright 1997-2006 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 <errno.h> |
| #include <netinet/in.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| |
| #ifdef __solaris__ |
| #include <fcntl.h> |
| #endif |
| #ifdef __linux__ |
| #include <linux/unistd.h> |
| #include <linux/sysctl.h> |
| #include <sys/utsname.h> |
| #include <netinet/ip.h> |
| |
| #define IPV6_MULTICAST_IF 17 |
| #ifndef SO_BSDCOMPAT |
| #define SO_BSDCOMPAT 14 |
| #endif |
| #endif |
| |
| #ifndef IPTOS_TOS_MASK |
| #define IPTOS_TOS_MASK 0x1e |
| #endif |
| #ifndef IPTOS_PREC_MASK |
| #define IPTOS_PREC_MASK 0xe0 |
| #endif |
| |
| #include "jvm.h" |
| #include "jni_util.h" |
| #include "net_util.h" |
| |
| #include "java_net_SocketOptions.h" |
| #include "java_net_PlainDatagramSocketImpl.h" |
| #include "java_net_NetworkInterface.h" |
| /************************************************************************ |
| * 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; |
| |
| #ifdef __linux__ |
| static jboolean isOldKernel; |
| #endif |
| |
| #if defined(__linux__) && defined(AF_INET6) |
| static jfieldID pdsi_multicastInterfaceID; |
| static jfieldID pdsi_loopbackID; |
| static jfieldID pdsi_ttlID; |
| #endif |
| |
| /* |
| * 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) { |
| |
| #ifdef __linux__ |
| struct utsname sysinfo; |
| #endif |
| 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); |
| |
| Java_java_net_InetAddress_init(env, 0); |
| Java_java_net_Inet4Address_init(env, 0); |
| Java_java_net_Inet6Address_init(env, 0); |
| Java_java_net_NetworkInterface_init(env, 0); |
| |
| #ifdef __linux__ |
| /* |
| * We need to determine if this is a 2.2 kernel. |
| */ |
| if (uname(&sysinfo) == 0) { |
| sysinfo.release[3] = '\0'; |
| isOldKernel = (strcmp(sysinfo.release, "2.2") == 0); |
| } else { |
| /* |
| * uname failed - move to plan B and examine /proc/version |
| * If this fails assume that /proc has changed and that |
| * this must be new /proc format and hence new kernel. |
| */ |
| FILE *fP; |
| isOldKernel = JNI_FALSE; |
| if ((fP = fopen("/proc/version", "r")) != NULL) { |
| char ver[25]; |
| if (fgets(ver, sizeof(ver), fP) != NULL) { |
| isOldKernel = (strstr(ver, "2.2.") != NULL); |
| } |
| fclose(fP); |
| } |
| } |
| |
| #ifdef AF_INET6 |
| pdsi_multicastInterfaceID = (*env)->GetFieldID(env, cls, "multicastInterface", "I"); |
| CHECK_NULL(pdsi_multicastInterfaceID); |
| pdsi_loopbackID = (*env)->GetFieldID(env, cls, "loopbackMode", "Z"); |
| CHECK_NULL(pdsi_loopbackID); |
| pdsi_ttlID = (*env)->GetFieldID(env, cls, "ttl", "I"); |
| CHECK_NULL(pdsi_ttlID); |
| #endif |
| |
| #endif |
| |
| } |
| |
| /* |
| * 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; |
| SOCKADDR him; |
| |
| 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, (struct sockaddr *)&him, &len, JNI_TRUE) != 0) { |
| return; |
| } |
| |
| if (NET_Bind(fd, (struct sockaddr *)&him, len) < 0) { |
| if (errno == EADDRINUSE || errno == EADDRNOTAVAIL || |
| errno == EPERM || errno == EACCES) { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "BindException", |
| "Bind failed"); |
| } else { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", |
| "Bind failed"); |
| } |
| return; |
| } |
| |
| /* intialize 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 (JVM_GetSockName(fd, (struct sockaddr *)&him, &len) == -1) { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", |
| "Error getting socket name"); |
| return; |
| } |
| |
| localport = NET_GetPortFromSockaddr((struct sockaddr *)&him); |
| |
| (*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 */ |
| SOCKADDR 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, (struct sockaddr *)&rmtaddr, &len, JNI_TRUE) != 0) { |
| return; |
| } |
| |
| #ifdef __linux__ |
| if (isOldKernel) { |
| int t = 0; |
| setsockopt(fd, SOL_SOCKET, SO_BSDCOMPAT, (char*) &t, sizeof(int)); |
| } else |
| #endif |
| { |
| if (JVM_Connect(fd, (struct sockaddr *)&rmtaddr, len) == -1) { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException", |
| "Connect failed"); |
| return; |
| } |
| } |
| } |
| |
| /* |
| * 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; |
| |
| #ifdef __linux__ |
| SOCKADDR addr; |
| int len; |
| #endif |
| |
| if (IS_NULL(fdObj)) { |
| return; |
| } |
| fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); |
| |
| #ifdef __linux__ |
| if (isOldKernel) { |
| int t = 1; |
| setsockopt(fd, SOL_SOCKET, SO_BSDCOMPAT, (char*) &t, sizeof(int)); |
| } else { |
| memset(&addr, 0, sizeof(addr)); |
| #ifdef AF_INET6 |
| if (ipv6_available()) { |
| struct sockaddr_in6 *him6 = (struct sockaddr_in6 *)&addr; |
| him6->sin6_family = AF_UNSPEC; |
| len = sizeof(struct sockaddr_in6); |
| } else |
| #endif |
| { |
| struct sockaddr_in *him4 = (struct sockaddr_in*)&addr; |
| him4->sin_family = AF_UNSPEC; |
| len = sizeof(struct sockaddr_in); |
| } |
| JVM_Connect(fd, (struct sockaddr *)&addr, len); |
| |
| // After disconnecting a UDP socket, Linux kernel will set |
| // local port to zero if the port number comes from implicit |
| // bind. Successive send/recv on the same socket will fail. |
| // So bind again with former port number here. |
| int localPort = 0; |
| if (JVM_GetSockName(fd, (struct sockaddr *)&addr, &len) == -1) { |
| return; |
| } |
| localPort = NET_GetPortFromSockaddr((struct sockaddr *)&addr); |
| if (localPort == 0) { |
| localPort = (*env)->GetIntField(env, this, pdsi_localPortID); |
| #ifdef AF_INET6 |
| if (((struct sockaddr*)&addr)->sa_family == AF_INET6) { |
| ((struct sockaddr_in6*)&addr)->sin6_port = htons(localPort); |
| } else |
| #endif /* AF_INET6 */ |
| { |
| ((struct sockaddr_in*)&addr)->sin_port = htons(localPort); |
| } |
| NET_Bind(fd, (struct sockaddr *)&addr, len); |
| } |
| } |
| #else |
| JVM_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; |
| |
| SOCKADDR rmtaddr, *rmtaddrP=&rmtaddr; |
| int len; |
| |
| 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); |
| |
| #ifdef __linux__ |
| if (connected && !isOldKernel) { |
| #else |
| if (connected) { |
| #endif |
| /* arg to NET_Sendto () null in this case */ |
| len = 0; |
| rmtaddrP = 0; |
| } else { |
| packetPort = (*env)->GetIntField(env, packet, dp_portID); |
| if (NET_InetAddressToSockaddr(env, packetAddress, packetPort, (struct sockaddr *)&rmtaddr, &len, JNI_TRUE) != 0) { |
| return; |
| } |
| } |
| |
| if (packetBufferLen > MAX_BUFFER_LEN) { |
| /* When JNI-ifying the JDK's IO routines, we turned |
| * read's and write's 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* alloc 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, "heap allocation failed"); |
| return; |
| } else { |
| mallocedPacket = JNI_TRUE; |
| } |
| } else { |
| fullPacket = &(BUF[0]); |
| } |
| |
| (*env)->GetByteArrayRegion(env, packetBuffer, packetBufferOffset, packetBufferLen, |
| (jbyte *)fullPacket); |
| #ifdef AF_INET6 |
| if (trafficClass != 0 && ipv6_available()) { |
| NET_SetTrafficClass((struct sockaddr *)&rmtaddr, trafficClass); |
| } |
| #endif /* AF_INET6 */ |
| |
| |
| /* |
| * 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, |
| (struct sockaddr *)rmtaddrP, len); |
| |
| if (ret < 0) { |
| switch (ret) { |
| case JVM_IO_ERR : |
| if (errno == ECONNREFUSED) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException", |
| "ICMP Port Unreachable"); |
| } else { |
| NET_ThrowByNameWithLastError(env, "java/io/IOException", "sendto failed"); |
| } |
| break; |
| |
| case JVM_IO_INTR: |
| JNU_ThrowByName(env, "java/io/InterruptedIOException", |
| "operation interrupted"); |
| break; |
| } |
| } |
| |
| 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; |
| SOCKADDR remote_addr; |
| int len; |
| 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()"); |
| } |
| if (timeout) { |
| int ret = NET_Timeout(fd, timeout); |
| if (ret == 0) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", |
| "Peek timed out"); |
| return ret; |
| } else if (ret == JVM_IO_ERR) { |
| if (errno == EBADF) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| } else { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Peek failed"); |
| } |
| return ret; |
| } else if (ret == JVM_IO_INTR) { |
| JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException", |
| "operation interrupted"); |
| return ret; /* WARNING: SHOULD WE REALLY RETURN -2??? */ |
| } |
| } |
| |
| len = SOCKADDR_LEN; |
| n = NET_RecvFrom(fd, buf, 1, MSG_PEEK, |
| (struct sockaddr *)&remote_addr, &len); |
| |
| if (n == JVM_IO_ERR) { |
| |
| #ifdef __solaris__ |
| if (errno == ECONNREFUSED) { |
| int orig_errno = errno; |
| (void) 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 { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Peek failed"); |
| } |
| } |
| return 0; |
| } else if (n == JVM_IO_INTR) { |
| JNU_ThrowByName(env, "java/io/InterruptedIOException", 0); |
| return 0; |
| } |
| |
| iaObj = NET_SockaddrToInetAddress(env, (struct sockaddr *)&remote_addr, &port); |
| family = (*env)->GetIntField(env, iaObj, ia_familyID) == IPv4? |
| AF_INET : AF_INET6; |
| if (family == AF_INET) { /* this api can't handle IPV6 addresses */ |
| int address = (*env)->GetIntField(env, iaObj, ia_addressID); |
| (*env)->SetIntField(env, addressObj, ia_addressID, 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; |
| SOCKADDR remote_addr; |
| int len; |
| int port; |
| |
| 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(fd, timeout); |
| if (ret == 0) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", |
| "Receive timed out"); |
| return -1; |
| } else if (ret == JVM_IO_ERR) { |
| #ifdef __linux__ |
| if (errno == EBADF) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| } else { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed"); |
| } |
| #else |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| #endif |
| return -1; |
| } else if (ret == JVM_IO_INTR) { |
| JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException", |
| "operation interrupted"); |
| return -1; |
| } |
| } |
| |
| if (packetBufferLen > MAX_BUFFER_LEN) { |
| |
| /* When JNI-ifying the JDK's IO routines, we turned |
| * read's and write's 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* alloc 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, "heap allocation failed"); |
| return -1; |
| } else { |
| mallocedPacket = JNI_TRUE; |
| } |
| } else { |
| fullPacket = &(BUF[0]); |
| } |
| |
| len = SOCKADDR_LEN; |
| n = NET_RecvFrom(fd, fullPacket, packetBufferLen, MSG_PEEK, |
| (struct sockaddr *)&remote_addr, &len); |
| /* truncate the data if the packet's length is too small */ |
| if (n > packetBufferLen) { |
| n = packetBufferLen; |
| } |
| if (n == JVM_IO_ERR) { |
| |
| #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 { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed"); |
| } |
| } |
| } else if (n == JVM_IO_INTR) { |
| (*env)->SetIntField(env, packet, dp_offsetID, 0); |
| (*env)->SetIntField(env, packet, dp_lengthID, 0); |
| JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException", |
| "operation interrupted"); |
| } 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, (struct sockaddr *)&remote_addr, packetAddress)) { |
| /* force a new InetAddress to be created */ |
| packetAddress = NULL; |
| } |
| } |
| if (packetAddress == NULL) { |
| packetAddress = NET_SockaddrToInetAddress(env, (struct sockaddr *)&remote_addr, &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((struct sockaddr *)&remote_addr); |
| } |
| /* 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; |
| SOCKADDR remote_addr; |
| int len; |
| jboolean retry; |
| #ifdef __linux__ |
| jboolean connected = JNI_FALSE; |
| jobject connectedAddress; |
| jint connectedPort; |
| jlong prevTime; |
| #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 |
| * read's and write's 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* alloc 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, "heap allocation failed"); |
| return; |
| } else { |
| mallocedPacket = JNI_TRUE; |
| } |
| } else { |
| fullPacket = &(BUF[0]); |
| } |
| |
| #ifdef __linux__ |
| /* |
| * On Linux with the 2.2 kernel we simulate connected datagrams by |
| * discarding packets |
| */ |
| if (isOldKernel) { |
| connected = (*env)->GetBooleanField(env, this, pdsi_connected); |
| if (connected) { |
| connectedAddress = (*env)->GetObjectField(env, this, pdsi_connectedAddress); |
| connectedPort = (*env)->GetIntField(env, this, pdsi_connectedPort); |
| |
| if (timeout) { |
| prevTime = JVM_CurrentTimeMillis(env, 0); |
| } |
| } |
| } |
| #endif |
| |
| do { |
| retry = JNI_FALSE; |
| |
| if (timeout) { |
| int ret = NET_Timeout(fd, timeout); |
| if (ret <= 0) { |
| if (ret == 0) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", |
| "Receive timed out"); |
| } else if (ret == JVM_IO_ERR) { |
| #ifdef __linux__ |
| if (errno == EBADF) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| } else { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed"); |
| } |
| #else |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); |
| #endif |
| } else if (ret == JVM_IO_INTR) { |
| JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException", |
| "operation interrupted"); |
| } |
| |
| if (mallocedPacket) { |
| free(fullPacket); |
| } |
| |
| return; |
| } |
| } |
| |
| /* |
| * Security Note: For Linux 2.2 with connected datagrams ensure that |
| * you receive into the stack/heap allocated buffer - do not attempt |
| * to receive directly into DatagramPacket's byte array. |
| * (ie: if the virtual machine support pinning don't use |
| * GetByteArrayElements or a JNI critical section and receive |
| * directly into the byte array) |
| */ |
| len = SOCKADDR_LEN; |
| n = NET_RecvFrom(fd, fullPacket, packetBufferLen, 0, |
| (struct sockaddr *)&remote_addr, &len); |
| /* truncate the data if the packet's length is too small */ |
| if (n > packetBufferLen) { |
| n = packetBufferLen; |
| } |
| if (n == JVM_IO_ERR) { |
| (*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 { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed"); |
| } |
| } |
| } else if (n == JVM_IO_INTR) { |
| (*env)->SetIntField(env, packet, dp_offsetID, 0); |
| (*env)->SetIntField(env, packet, dp_lengthID, 0); |
| JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException", |
| "operation interrupted"); |
| } else { |
| int port; |
| jobject packetAddress; |
| |
| /* |
| * If we are connected then we know that the datagram that we have |
| * received is from the address that we are connected too. However |
| * on Linux with 2.2 kernel we have to simulate this behaviour by |
| * discarding any datagrams that aren't from the connected address. |
| */ |
| #ifdef __linux__ |
| if (isOldKernel && connected) { |
| |
| if (NET_GetPortFromSockaddr((struct sockaddr *)&remote_addr) != connectedPort || |
| !NET_SockaddrEqualsInetAddress(env, (struct sockaddr *)&remote_addr, connectedAddress)) { |
| |
| /* |
| * Discard the datagram as it's not from the connected |
| * address |
| */ |
| retry = JNI_TRUE; |
| |
| /* |
| * Adjust timeout if necessary to ensure that we adhere to |
| * timeout semantics. |
| */ |
| if (timeout) { |
| jlong newTime = JVM_CurrentTimeMillis(env, 0); |
| timeout -= (newTime - prevTime); |
| if (timeout <= 0) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", |
| "Receive timed out"); |
| if (mallocedPacket) { |
| free(fullPacket); |
| } |
| return; |
| } |
| prevTime = newTime; |
| } |
| |
| continue; |
| } |
| } |
| #endif |
| |
| /* |
| * 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, (struct sockaddr *)&remote_addr, packetAddress)) { |
| /* force a new InetAddress to be created */ |
| packetAddress = NULL; |
| } |
| } |
| if (packetAddress == NULL) { |
| packetAddress = NET_SockaddrToInetAddress(env, (struct sockaddr *)&remote_addr, &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((struct sockaddr *)&remote_addr); |
| } |
| /* 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 fd; |
| |
| int t = 1; |
| |
| if (IS_NULL(fdObj)) { |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket closed"); |
| return; |
| } else { |
| #ifdef AF_INET6 |
| if (ipv6_available()) { |
| fd = JVM_Socket(AF_INET6, SOCK_DGRAM, 0); |
| } else |
| #endif /* AF_INET6 */ |
| { |
| fd = JVM_Socket(AF_INET, SOCK_DGRAM, 0); |
| } |
| } |
| if (fd == JVM_IO_ERR) { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", |
| "Error creating socket"); |
| return; |
| } |
| |
| setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char*) &t, sizeof(int)); |
| |
| #ifdef __linux__ |
| if (isOldKernel) { |
| setsockopt(fd, SOL_SOCKET, SO_BSDCOMPAT, (char*) &t, sizeof(int)); |
| } |
| |
| #ifdef AF_INET6 |
| /* |
| * 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 (ipv6_available()) { |
| int ttl = 1; |
| setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&ttl, |
| sizeof(ttl)); |
| |
| if (isOldKernel) { |
| (*env)->SetIntField(env, this, pdsi_ttlID, ttl); |
| } |
| } |
| #endif |
| |
| #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; |
| static jfieldID ia_addressID; |
| 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); |
| c = (*env)->FindClass(env,"java/net/InetAddress"); |
| CHECK_NULL(c); |
| ia_addressID = (*env)->GetFieldID(env, c, "address", "I"); |
| CHECK_NULL(ia_addressID); |
| } |
| |
| 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 |
| */ |
| for (i = 0; i < len; i++) { |
| addr = (*env)->GetObjectArrayElement(env, addrArray, i); |
| if ((*env)->GetIntField(env, addr, ia_familyID) == IPv4) { |
| in.s_addr = htonl((*env)->GetIntField(env, addr, ia_addressID)); |
| break; |
| } |
| } |
| |
| if (JVM_SetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_IF, |
| (const char*)&in, sizeof(in)) < 0) { |
| NET_ThrowByNameWithLastError(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 (JVM_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 { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", |
| "Error setting socket option"); |
| } |
| return; |
| } |
| |
| #ifdef __linux__ |
| /* |
| * Linux 2.2 kernel doesn't support IPV6_MULTICAST_IF socket |
| * option so record index for later retrival. |
| */ |
| if (isOldKernel) { |
| (*env)->SetIntField(env, this, pdsi_multicastInterfaceID, |
| (jint)index); |
| } |
| #endif |
| } |
| |
| /* |
| * 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) { |
| static jfieldID ia_addressID; |
| struct in_addr in; |
| |
| if (ia_addressID == NULL) { |
| jclass c = (*env)->FindClass(env,"java/net/InetAddress"); |
| CHECK_NULL(c); |
| ia_addressID = (*env)->GetFieldID(env, c, "address", "I"); |
| CHECK_NULL(ia_addressID); |
| } |
| |
| in.s_addr = htonl( (*env)->GetIntField(env, value, ia_addressID) ); |
| |
| if (JVM_SetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_IF, |
| (const char*)&in, sizeof(in)) < 0) { |
| NET_ThrowByNameWithLastError(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 |
| * On Linux 2.2 record interface index as can't |
| * query the multicast interface. |
| * |
| * 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 |
| * On Linux 2.2 record interface index as can't |
| * query the multicast interface. |
| * |
| */ |
| 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 __solaris__ |
| 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 |
| #ifdef __linux__ |
| mcast_set_if_by_addr_v4(env, this, fd, value); |
| if (ipv6_available()) { |
| mcast_set_if_by_addr_v6(env, this, fd, value); |
| } |
| #endif |
| } |
| |
| if (opt == java_net_SocketOptions_IP_MULTICAST_IF2) { |
| /* |
| * value is a NetworkInterface. |
| */ |
| #ifdef __solaris__ |
| 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 |
| #ifdef __linux__ |
| mcast_set_if_by_if_v4(env, this, fd, value); |
| if (ipv6_available()) { |
| mcast_set_if_by_if_v6(env, this, fd, value); |
| } |
| #endif |
| } |
| } |
| |
| /* |
| * 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) { |
| NET_ThrowByNameWithLastError(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) { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Error setting socket option"); |
| return; |
| } |
| |
| #ifdef __linux__ |
| /* |
| * Can't query IPV6_MULTICAST_LOOP on Linux 2.2 kernel so |
| * store it in impl so that we can simulate getsockopt. |
| */ |
| if (isOldKernel) { |
| (*env)->SetBooleanField(env, this, pdsi_loopbackID, on); |
| } |
| #endif |
| } |
| |
| /* |
| * Sets the multicast loopback mode. |
| */ |
| static void setMulticastLoopbackMode(JNIEnv *env, jobject this, int fd, |
| jint opt, jobject value) { |
| #ifdef __solaris__ |
| if (ipv6_available()) { |
| mcast_set_loop_v6(env, this, fd, value); |
| } else { |
| mcast_set_loop_v4(env, this, fd, value); |
| } |
| #endif |
| #ifdef __linux__ |
| mcast_set_loop_v4(env, this, fd, value); |
| if (ipv6_available()) { |
| mcast_set_loop_v6(env, this, fd, value); |
| } |
| #endif |
| } |
| |
| /* |
| * Class: java_net_PlainDatagramSocketImpl |
| * Method: socketSetOption |
| * Signature: (ILjava/lang/Object;)V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_net_PlainDatagramSocketImpl_socketSetOption(JNIEnv *env, |
| jobject this, |
| jint opt, |
| jobject value) { |
| int fd; |
| int level, optname, optlen; |
| union { |
| int i; |
| char c; |
| } optval; |
| |
| /* |
| * 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 seperately |
| */ |
| 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, JNU_JAVANETPKG "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.i = (*env)->GetIntField(env, value, fid); |
| optlen = sizeof(optval.i); |
| break; |
| } |
| |
| case java_net_SocketOptions_SO_REUSEADDR: |
| 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.i = (on ? 1 : 0); |
| optlen = sizeof(optval.i); |
| |
| break; |
| } |
| |
| default : |
| JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", |
| "Socket option not supported by PlainDatagramSocketImp"); |
| break; |
| |
| } |
| |
| if (NET_SetSockOpt(fd, level, optname, (const void *)&optval, optlen) < 0) { |
| NET_ThrowByNameWithLastError(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 or |
| * obtain from impl is Linux 2.2 kernel |
| * 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; |
| |
| #ifdef AF_INET6 |
| if (ipv6_available()) { |
| isIPV4 = JNI_FALSE; |
| } |
| #endif |
| |
| /* |
| * IPv4 implementation |
| */ |
| if (isIPV4) { |
| static jclass inet4_class; |
| static jmethodID inet4_ctrID; |
| static jfieldID inet4_addrID; |
| |
| static jclass ni_class; |
| static jmethodID ni_ctrID; |
| static jfieldID ni_indexID; |
| static jfieldID ni_addrsID; |
| |
| jobjectArray addrArray; |
| jobject addr; |
| jobject ni; |
| |
| struct in_addr in; |
| struct in_addr *inP = ∈ |
| int len = sizeof(struct in_addr); |
| |
| #ifdef __linux__ |
| struct ip_mreqn mreqn; |
| if (isOldKernel) { |
| inP = (struct in_addr *)&mreqn; |
| len = sizeof(struct ip_mreqn); |
| } |
| #endif |
| |
| if (JVM_GetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_IF, |
| (char *)inP, &len) < 0) { |
| NET_ThrowByNameWithLastError(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_addrID = (*env)->GetFieldID(env, c, "address", "I"); |
| CHECK_NULL_RETURN(inet4_addrID, 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); |
| |
| #ifdef __linux__ |
| (*env)->SetIntField(env, addr, inet4_addrID, |
| (isOldKernel ? ntohl(mreqn.imr_address.s_addr) : ntohl(in.s_addr)) ); |
| #else |
| (*env)->SetIntField(env, addr, inet4_addrID, ntohl(in.s_addr)); |
| #endif |
| |
| /* |
| * 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_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); |
| return ni; |
| } |
| |
| |
| #ifdef AF_INET6 |
| /* |
| * 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 jmethodID ia_anyLocalAddressID; |
| |
| int index; |
| int len = sizeof(index); |
| |
| jobjectArray addrArray; |
| jobject addr; |
| jobject ni; |
| |
| #ifdef __linux__ |
| /* |
| * Linux 2.2 kernel doesn't support IPV6_MULTICAST_IF socke option |
| * so use cached index. |
| */ |
| if (isOldKernel) { |
| index = (*env)->GetIntField(env, this, pdsi_multicastInterfaceID); |
| } else |
| #endif |
| { |
| if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, |
| (char*)&index, &len) < 0) { |
| NET_ThrowByNameWithLastError(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_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_getByIndex(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); |
| return ni; |
| } |
| #endif |
| 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 seperately |
| */ |
| 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 */ |
| SOCKADDR him; |
| int len = 0; |
| int port; |
| jobject iaObj; |
| |
| len = SOCKADDR_LEN; |
| |
| if (getsockname(fd, (struct sockaddr *)&him, &len) == -1) { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", |
| "Error getting socket name"); |
| return NULL; |
| } |
| iaObj = NET_SockaddrToInetAddress(env, (struct sockaddr *)&him, &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, JNU_JAVANETPKG "SocketException", "Invalid option"); |
| return NULL; |
| } |
| |
| /* |
| * IP_MULTICAST_LOOP socket option isn't available on Linux 2.2 |
| * kernel with IPv6 so return value stored in impl. |
| */ |
| #if defined(AF_INET6) && defined(__linux__) |
| if (isOldKernel && opt == java_net_SocketOptions_IP_MULTICAST_LOOP && |
| level == IPPROTO_IPV6) { |
| int mode = (int)(*env)->GetBooleanField(env, this, pdsi_loopbackID); |
| return createBoolean(env, mode); |
| } |
| #endif |
| |
| 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) { |
| NET_ThrowByNameWithLastError(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_SNDBUF: |
| case java_net_SocketOptions_SO_RCVBUF: |
| case java_net_SocketOptions_IP_TOS: |
| return createInteger(env, optval.i); |
| |
| } |
| |
| /* should never rearch 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 (JVM_SetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&ittl, |
| sizeof(ittl)) < 0) { |
| NET_ThrowByNameWithLastError(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 (JVM_SetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, |
| (char*)&ittl, sizeof(ittl)) < 0) { |
| NET_ThrowByNameWithLastError(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 __solaris__ |
| if (ipv6_available()) { |
| setHopLimit(env, fd, ttl); |
| } else { |
| setTTL(env, fd, ttl); |
| } |
| #endif |
| #ifdef __linux__ |
| setTTL(env, fd, ttl); |
| if (ipv6_available()) { |
| setHopLimit(env, fd, ttl); |
| if (isOldKernel) { |
| (*env)->SetIntField(env, this, pdsi_ttlID, ttl); |
| } |
| } |
| #endif |
| } |
| |
| /* |
| * 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 */ |
| #ifdef AF_INET6 |
| if (ipv6_available()) { |
| int ttl = 0; |
| int len = sizeof(ttl); |
| |
| #ifdef __linux__ |
| /* |
| * Linux 2.2 kernel doesn't support IPV6_MULTICAST_HOPS socket option |
| */ |
| if (isOldKernel) { |
| return (*env)->GetIntField(env, this, pdsi_ttlID); |
| } |
| #endif |
| |
| if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, |
| (char*)&ttl, &len) < 0) { |
| NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", |
| "Error getting socket option"); |
| return -1; |
| } |
| return (jint)ttl; |
| } else |
| #endif /* AF_INET6 */ |
| { |
| u_char ttl = 0; |
| int len = sizeof(ttl); |
| if (JVM_GetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_TTL, |
| (char*)&ttl, &len) < 0) { |
| NET_ThrowByNameWithLastError(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. |
| */ |
| #ifdef AF_INET6 |
| ipv6_join_leave = ipv6_available(); |
| |
| #ifdef __linux__ |
| if ((*env)->GetIntField(env, iaObj, ia_familyID) == IPv4) { |
| ipv6_join_leave = JNI_FALSE; |
| } |
| #endif |
| |
| #else |
| /* |
| * IPv6 not compiled in |
| */ |
| 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__) && defined(AF_INET6) |
| 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((*env)->GetIntField(env, iaObj, ia_addressID)); |
| 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((*env)->GetIntField(env, iaObj, ia_addressID)); |
| #ifdef __linux__ |
| mname.imr_address.s_addr = htonl((*env)->GetIntField(env, addr, ia_addressID)); |
| #else |
| mname.imr_interface.s_addr = htonl((*env)->GetIntField(env, addr, ia_addressID)); |
| #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__) && defined(AF_INET6) |
| if (ipv6_available()) { |
| |
| int index; |
| int len = sizeof(index); |
| |
| if (isOldKernel) { |
| index = (*env)->GetIntField(env, this, pdsi_multicastInterfaceID); |
| } else { |
| if (JVM_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((*env)->GetIntField(env, iaObj, ia_addressID)); |
| 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 = ∈ |
| int len = sizeof(struct in_addr); |
| |
| #ifdef __linux__ |
| struct ip_mreqn mreqn; |
| if (isOldKernel) { |
| inP = (struct in_addr *)&mreqn; |
| len = sizeof(struct ip_mreqn); |
| } |
| #endif |
| 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 = |
| (isOldKernel ? mreqn.imr_address.s_addr : in.s_addr); |
| |
| #else |
| mname.imr_interface.s_addr = in.s_addr; |
| #endif |
| mname.imr_multiaddr.s_addr = htonl((*env)->GetIntField(env, iaObj, ia_addressID)); |
| mname_len = sizeof(struct ip_mreq); |
| } |
| } |
| |
| |
| /* |
| * Join the multicast group. |
| */ |
| if (JVM_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_MEMERSHIP can't handle IPv4-mapped |
| * addresses so we have to use IP_ADD_MEMERSHIP for IPv4 multicast |
| * groups. However if the socket is an IPv6 socket then then setsockopt |
| * should reurn ENOPROTOOPT. We assume this will be fixed in Linux |
| * at some stage. |
| */ |
| #if defined(__linux__) && defined(AF_INET6) |
| 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"); |
| } |
| } |
| } |
| |
| /* |
| * 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. |
| */ |
| #ifdef AF_INET6 |
| { |
| struct ipv6_mreq mname6; |
| jbyteArray ipaddress; |
| jbyte caddr[16]; |
| jint family; |
| jint address; |
| family = (*env)->GetIntField(env, iaObj, ia_familyID) == IPv4? AF_INET : AF_INET6; |
| if (family == AF_INET) { /* will convert to IPv4-mapped address */ |
| memset((char *) caddr, 0, 16); |
| address = (*env)->GetIntField(env, iaObj, ia_addressID); |
| |
| 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 { |
| ipaddress = (*env)->GetObjectField(env, iaObj, ia6_ipaddressID); |
| (*env)->GetByteArrayRegion(env, ipaddress, 0, 16, caddr); |
| } |
| |
| memcpy((void *)&(mname6.ipv6mr_multiaddr), caddr, sizeof(struct in6_addr)); |
| if (IS_NULL(niObj)) { |
| int index; |
| int len = sizeof(index); |
| |
| #ifdef __linux__ |
| /* |
| * 2.2 kernel doens't support IPV6_MULTICAST_IF socket option |
| */ |
| if (isOldKernel) { |
| index = (*env)->GetIntField(env, this, pdsi_multicastInterfaceID); |
| } else |
| #endif |
| { |
| if (JVM_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 && !isOldKernel) { |
| int rt_index = getDefaultIPv6Interface(&(mname6.ipv6mr_multiaddr)); |
| if (rt_index > 0) { |
| index = rt_index; |
| } |
| } |
| #endif |
| |
| mname6.ipv6mr_interface = index; |
| } else { |
| jint idx = (*env)->GetIntField(env, niObj, ni_indexID); |
| mname6.ipv6mr_interface = idx; |
| } |
| |
| /* Join the multicast group */ |
| if (JVM_SetSockOpt(fd, IPPROTO_IPV6, (join ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP), |
| (char *) &mname6, sizeof (mname6)) < 0) { |
| |
| if (join) { |
| NET_ThrowCurrent(env, "setsockopt IPV6_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 IPV6_DROP_MEMBERSHIP failed"); |
| } |
| } |
| } |
| } |
| #endif |
| } |
| |
| /* |
| * 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); |
| } |