diff --git a/build.gradle b/build.gradle index 40aee8a..ad080fc 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,7 @@ plugins { id 'signing' id "io.github.gradle-nexus.publish-plugin" version "2.0.0-rc-1" id "com.google.osdetector" version "1.7.3" + id 'org.xbib.gradle.plugin.c' version '3.1.0' } wrapper { @@ -11,8 +12,6 @@ wrapper { distributionType = Wrapper.DistributionType.BIN } -apply plugin: 'com.google.osdetector' - ext { user = 'joerg' name = 'netty' @@ -30,11 +29,17 @@ ext { organizationUrl = 'https://xbib.org' } +apply plugin: 'com.google.osdetector' + subprojects { - apply from: rootProject.file('gradle/repositories/maven.gradle') - apply from: rootProject.file('gradle/compile/java.gradle') - apply from: rootProject.file('gradle/test/junit5.gradle') - apply from: rootProject.file('gradle/publish/maven.gradle') + if (it.name.endsWith('-native')) { + apply from: rootProject.file('gradle/compile/c.gradle') + } else { + apply from: rootProject.file('gradle/repositories/maven.gradle') + apply from: rootProject.file('gradle/compile/java.gradle') + apply from: rootProject.file('gradle/test/junit5.gradle') + apply from: rootProject.file('gradle/publish/maven.gradle') + } } apply from: rootProject.file('gradle/publish/sonatype.gradle') apply from: rootProject.file('gradle/publish/forgejo.gradle') diff --git a/gradle.properties b/gradle.properties index d830a39..7ec82ca 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ group = org.xbib.netty name = netty -version = 4.1.104 +version = 4.1.105.0 diff --git a/gradle/compile/c.gradle b/gradle/compile/c.gradle new file mode 100644 index 0000000..d52a2a0 --- /dev/null +++ b/gradle/compile/c.gradle @@ -0,0 +1,3 @@ + +apply plugin: 'base' +apply plugin: 'org.xbib.gradle.plugin.c' diff --git a/netty-channel-epoll-native/build.gradle b/netty-channel-epoll-native/build.gradle index aac2e31..f526963 100644 --- a/netty-channel-epoll-native/build.gradle +++ b/netty-channel-epoll-native/build.gradle @@ -1,21 +1,12 @@ -apply plugin: 'com.google.osdetector' - -dependencies { - testImplementation project(':netty-channel-unix') - testImplementation project(':netty-channel-epoll') - testImplementation project(':netty-testsuite') - testImplementation project(':netty-handler') - testImplementation testLibs.assertj - testImplementation testLibs.rerunner.jupiter - testRuntimeOnly project(path: ':netty-tcnative-boringssl-static', configuration: osdetector.classifier) -} task nettyEpollLinuxX8664(type: Jar) { - archiveBaseName.set('netty-channel-epoll-native') + destinationDirectory.set(project.layout.buildDirectory.dir('libs')) + archiveBaseName.set(project.name + '-' + project.version) + archiveExtension.set('jar') archiveClassifier.set('linux-x86_64') version rootProject.version - from (sourceSets.main.output) { + from (project.layout.projectDirectory.dir('src/main/resources')) { include 'META-INF/native/libnetty_transport_native_epoll_x86_64.so' } } @@ -25,7 +16,13 @@ configurations { 'linux-x86_64' { canBeConsumed = true canBeResolved = false - extendsFrom runtimeOnly + attributes { + attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.LIBRARY)) + attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)) + attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, Bundling.EXTERNAL)) + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, JavaVersion.current().majorVersion.toInteger()) + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, 'linux-x86_64')) + } } } diff --git a/netty-channel-epoll-native/src/main/c/netty_epoll_linuxsocket.c b/netty-channel-epoll-native/src/main/c/netty_epoll_linuxsocket.c new file mode 100644 index 0000000..7a2ae82 --- /dev/null +++ b/netty-channel-epoll-native/src/main/c/netty_epoll_linuxsocket.c @@ -0,0 +1,909 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/* + * Since glibc 2.8, the _GNU_SOURCE feature test macro must be defined + * (before including any header files) in order to obtain the + * definition of the ucred structure. See + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include // SOL_UDP +#include +#include // TCP_NOTSENT_LOWAT is a linux specific define +#include "netty_epoll_linuxsocket.h" +#include "netty_epoll_vmsocket.h" +#include "netty_unix_errors.h" +#include "netty_unix_filedescriptor.h" +#include "netty_unix_jni.h" +#include "netty_unix_socket.h" +#include "netty_unix_util.h" + +#define LINUXSOCKET_CLASSNAME "io/netty/channel/epoll/LinuxSocket" + +// TCP_FASTOPEN is defined in linux 3.7. We define this here so older kernels can compile. +#ifndef TCP_FASTOPEN +#define TCP_FASTOPEN 23 +#endif + +// TCP_FASTOPEN_CONNECT is defined in linux 4.11. We define this here so older kernels can compile. +#ifndef TCP_FASTOPEN_CONNECT +#define TCP_FASTOPEN_CONNECT 30 +#endif + +// TCP_NOTSENT_LOWAT is defined in linux 3.12. We define this here so older kernels can compile. +#ifndef TCP_NOTSENT_LOWAT +#define TCP_NOTSENT_LOWAT 25 +#endif + +// SO_BUSY_POLL is defined in linux 3.11. We define this here so older kernels can compile. +#ifndef SO_BUSY_POLL +#define SO_BUSY_POLL 46 +#endif + +// UDP_GRO is defined in linux 5. We define this here so older kernels can compile. +#ifndef UDP_GRO +#define UDP_GRO 104 +#endif + +static jweak peerCredentialsClassWeak = NULL; +static jmethodID peerCredentialsMethodId = NULL; + +static jfieldID fileChannelFieldId = NULL; +static jfieldID transferredFieldId = NULL; +static jfieldID fdFieldId = NULL; +static jfieldID fileDescriptorFieldId = NULL; + +// JNI Registered Methods Begin +static jint netty_epoll_linuxsocket_newVSockStreamFd(JNIEnv* env, jclass clazz) { + int fd = netty_unix_socket_nonBlockingSocket(AF_VSOCK, SOCK_STREAM, 0); + if (fd == -1) { + return -errno; + } + return fd; +} + +static jint netty_epoll_linuxsocket_bindVSock(JNIEnv* env, jclass clazz, jint fd, jint cid, jint port) { + struct sockaddr_vm addr; + memset(&addr, 0, sizeof(struct sockaddr_vm)); + + addr.svm_family = AF_VSOCK; + addr.svm_port = port; + addr.svm_cid = cid; + + int res = bind(fd, (struct sockaddr*) &addr, sizeof(struct sockaddr_vm)); + + if (res == -1) { + return -errno; + } + return res; +} + +static jint netty_epoll_linuxsocket_connectVSock(JNIEnv* env, jclass clazz, jint fd, jint cid, jint port) { + struct sockaddr_vm addr; + memset(&addr, 0, sizeof(struct sockaddr_vm)); + addr.svm_family = AF_VSOCK; + addr.svm_port = port; + addr.svm_cid = cid; + + int res; + int err; + do { + res = connect(fd, (struct sockaddr*) &addr, sizeof(struct sockaddr_vm)); + } while (res == -1 && ((err = errno) == EINTR)); + + if (res == -1) { + return -errno; + } + return res; +} + +static jbyteArray createVSockAddressArray(JNIEnv* env, const struct sockaddr_vm* addr) { + jbyteArray bArray = (*env)->NewByteArray(env, 8); + if (bArray == NULL) { + return NULL; + } + + unsigned int cid = (addr->svm_cid); + unsigned int port = (addr->svm_port); + + unsigned char a[4]; + a[0] = cid >> 24; + a[1] = cid >> 16; + a[2] = cid >> 8; + a[3] = cid; + (*env)->SetByteArrayRegion(env, bArray, 0, 4, (jbyte*) &a); + + a[0] = port >> 24; + a[1] = port >> 16; + a[2] = port >> 8; + a[3] = port; + (*env)->SetByteArrayRegion(env, bArray, 4, 4, (jbyte*) &a); + return bArray; +} + +static jbyteArray netty_epoll_linuxsocket_remoteVSockAddress(JNIEnv* env, jclass clazz, jint fd) { + struct sockaddr_vm addr = { 0 }; + socklen_t len = sizeof(addr); + if (getpeername(fd, (struct sockaddr*) &addr, &len) == -1) { + return NULL; + } + return createVSockAddressArray(env, &addr); +} + +static jbyteArray netty_epoll_linuxsocket_localVSockAddress(JNIEnv* env, jclass clazz, jint fd) { + struct sockaddr_vm addr = { 0 }; + socklen_t len = sizeof(addr); + if (getsockname(fd, (struct sockaddr*) &addr, &len) == -1) { + return NULL; + } + return createVSockAddressArray(env, &addr); +} + +static void netty_epoll_linuxsocket_setTimeToLive(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_TTL, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setIpMulticastLoop(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jint optval) { + if (ipv6 == JNI_TRUE) { + u_int val = (u_int) optval; + netty_unix_socket_setOption(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, sizeof(val)); + } else { + u_char val = (u_char) optval; + netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_MULTICAST_LOOP, &val, sizeof(val)); + } +} + +static void netty_epoll_linuxsocket_setInterface(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jbyteArray interfaceAddress, jint scopeId, jint interfaceIndex) { + struct sockaddr_storage interfaceAddr; + socklen_t interfaceAddrSize; + struct sockaddr_in* interfaceIpAddr; + + memset(&interfaceAddr, 0, sizeof(interfaceAddr)); + + if (ipv6 == JNI_TRUE) { + if (interfaceIndex == -1) { + netty_unix_errors_throwIOException(env, "Unable to find network index"); + return; + } + netty_unix_socket_setOption(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &interfaceIndex, sizeof(interfaceIndex)); + } else { + if (netty_unix_socket_initSockaddr(env, ipv6, interfaceAddress, scopeId, 0, &interfaceAddr, &interfaceAddrSize) == -1) { + netty_unix_errors_throwIOException(env, "Could not init sockaddr"); + return; + } + + interfaceIpAddr = (struct sockaddr_in*) &interfaceAddr; + netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_MULTICAST_IF, &interfaceIpAddr->sin_addr, sizeof(interfaceIpAddr->sin_addr)); + } +} + +static void netty_epoll_linuxsocket_setTcpCork(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_CORK, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpQuickAck(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_QUICKACK, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpDeferAccept(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpNotSentLowAt(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_NOTSENT_LOWAT, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpFastOpen(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_FASTOPEN, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpKeepIdle(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_KEEPIDLE, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpKeepIntvl(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpKeepCnt(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_KEEPCNT, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpUserTimeout(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setIpFreeBind(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_FREEBIND, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setIpTransparent(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, SOL_IP, IP_TRANSPARENT, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setIpRecvOrigDestAddr(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_RECVORIGDSTADDR, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setSoBusyPoll(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_BUSY_POLL, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_joinGroup(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jbyteArray groupAddress, jbyteArray interfaceAddress, jint scopeId, jint interfaceIndex) { + struct sockaddr_storage groupAddr; + socklen_t groupAddrSize; + struct sockaddr_storage interfaceAddr; + socklen_t interfaceAddrSize; + struct sockaddr_in* groupIpAddr; + struct sockaddr_in* interfaceIpAddr; + struct ip_mreq mreq; + + struct sockaddr_in6* groupIp6Addr; + struct ipv6_mreq mreq6; + + memset(&groupAddr, 0, sizeof(groupAddr)); + memset(&interfaceAddr, 0, sizeof(interfaceAddr)); + + if (netty_unix_socket_initSockaddr(env, ipv6, groupAddress, scopeId, 0, &groupAddr, &groupAddrSize) == -1) { + netty_unix_errors_throwIOException(env, "Could not init sockaddr for groupAddress"); + return; + } + + switch (groupAddr.ss_family) { + case AF_INET: + if (netty_unix_socket_initSockaddr(env, ipv6, interfaceAddress, scopeId, 0, &interfaceAddr, &interfaceAddrSize) == -1) { + netty_unix_errors_throwIOException(env, "Could not init sockaddr for interfaceAddr"); + return; + } + + interfaceIpAddr = (struct sockaddr_in*) &interfaceAddr; + groupIpAddr = (struct sockaddr_in*) &groupAddr; + + memcpy(&mreq.imr_multiaddr, &groupIpAddr->sin_addr, sizeof(groupIpAddr->sin_addr)); + memcpy(&mreq.imr_interface, &interfaceIpAddr->sin_addr, sizeof(interfaceIpAddr->sin_addr)); + netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + break; + case AF_INET6: + if (interfaceIndex == -1) { + netty_unix_errors_throwIOException(env, "Unable to find network index"); + return; + } + mreq6.ipv6mr_interface = interfaceIndex; + + groupIp6Addr = (struct sockaddr_in6*) &groupAddr; + memcpy(&mreq6.ipv6mr_multiaddr, &groupIp6Addr->sin6_addr, sizeof(groupIp6Addr->sin6_addr)); + netty_unix_socket_setOption(env, fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6, sizeof(mreq6)); + break; + default: + netty_unix_errors_throwIOException(env, "Address family not supported"); + break; + } +} + +static void netty_epoll_linuxsocket_joinSsmGroup(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jbyteArray groupAddress, jbyteArray interfaceAddress, jint scopeId, jint interfaceIndex, jbyteArray sourceAddress) { + struct sockaddr_storage groupAddr; + socklen_t groupAddrSize; + struct sockaddr_storage interfaceAddr; + socklen_t interfaceAddrSize; + struct sockaddr_storage sourceAddr; + socklen_t sourceAddrSize; + struct sockaddr_in* groupIpAddr; + struct sockaddr_in* interfaceIpAddr; + struct sockaddr_in* sourceIpAddr; + struct ip_mreq_source mreq; + + struct group_source_req mreq6; + + memset(&groupAddr, 0, sizeof(groupAddr)); + memset(&sourceAddr, 0, sizeof(sourceAddr)); + memset(&interfaceAddr, 0, sizeof(interfaceAddr)); + + if (netty_unix_socket_initSockaddr(env, ipv6, groupAddress, scopeId, 0, &groupAddr, &groupAddrSize) == -1) { + netty_unix_errors_throwIOException(env, "Could not init sockaddr for groupAddress"); + return; + } + + if (netty_unix_socket_initSockaddr(env, ipv6, sourceAddress, scopeId, 0, &sourceAddr, &sourceAddrSize) == -1) { + netty_unix_errors_throwIOException(env, "Could not init sockaddr for sourceAddress"); + return; + } + + switch (groupAddr.ss_family) { + case AF_INET: + if (netty_unix_socket_initSockaddr(env, ipv6, interfaceAddress, scopeId, 0, &interfaceAddr, &interfaceAddrSize) == -1) { + netty_unix_errors_throwIOException(env, "Could not init sockaddr for interfaceAddress"); + return; + } + interfaceIpAddr = (struct sockaddr_in*) &interfaceAddr; + groupIpAddr = (struct sockaddr_in*) &groupAddr; + sourceIpAddr = (struct sockaddr_in*) &sourceAddr; + memcpy(&mreq.imr_multiaddr, &groupIpAddr->sin_addr, sizeof(groupIpAddr->sin_addr)); + memcpy(&mreq.imr_interface, &interfaceIpAddr->sin_addr, sizeof(interfaceIpAddr->sin_addr)); + memcpy(&mreq.imr_sourceaddr, &sourceIpAddr->sin_addr, sizeof(sourceIpAddr->sin_addr)); + netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, &mreq, sizeof(mreq)); + break; + case AF_INET6: + if (interfaceIndex == -1) { + netty_unix_errors_throwIOException(env, "Unable to find network index"); + return; + } + mreq6.gsr_group = groupAddr; + mreq6.gsr_interface = interfaceIndex; + mreq6.gsr_source = sourceAddr; + netty_unix_socket_setOption(env, fd, IPPROTO_IPV6, MCAST_JOIN_SOURCE_GROUP, &mreq6, sizeof(mreq6)); + break; + default: + netty_unix_errors_throwIOException(env, "Address family not supported"); + break; + } +} + +static void netty_epoll_linuxsocket_leaveGroup(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jbyteArray groupAddress, jbyteArray interfaceAddress, jint scopeId, jint interfaceIndex) { + struct sockaddr_storage groupAddr; + socklen_t groupAddrSize; + + struct sockaddr_storage interfaceAddr; + socklen_t interfaceAddrSize; + struct sockaddr_in* groupIpAddr; + struct sockaddr_in* interfaceIpAddr; + struct ip_mreq mreq; + + struct sockaddr_in6* groupIp6Addr; + struct ipv6_mreq mreq6; + + memset(&groupAddr, 0, sizeof(groupAddr)); + memset(&interfaceAddr, 0, sizeof(interfaceAddr)); + + if (netty_unix_socket_initSockaddr(env, ipv6, groupAddress, scopeId, 0, &groupAddr, &groupAddrSize) == -1) { + netty_unix_errors_throwIOException(env, "Could not init sockaddr for groupAddress"); + return; + } + + switch (groupAddr.ss_family) { + case AF_INET: + if (netty_unix_socket_initSockaddr(env, ipv6, interfaceAddress, scopeId, 0, &interfaceAddr, &interfaceAddrSize) == -1) { + netty_unix_errors_throwIOException(env, "Could not init sockaddr for interfaceAddress"); + return; + } + interfaceIpAddr = (struct sockaddr_in*) &interfaceAddr; + groupIpAddr = (struct sockaddr_in*) &groupAddr; + + memcpy(&mreq.imr_multiaddr, &groupIpAddr->sin_addr, sizeof(groupIpAddr->sin_addr)); + memcpy(&mreq.imr_interface, &interfaceIpAddr->sin_addr, sizeof(interfaceIpAddr->sin_addr)); + netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); + break; + case AF_INET6: + if (interfaceIndex == -1) { + netty_unix_errors_throwIOException(env, "Unable to find network index"); + return; + } + mreq6.ipv6mr_interface = interfaceIndex; + + groupIp6Addr = (struct sockaddr_in6*) &groupAddr; + memcpy(&mreq6.ipv6mr_multiaddr, &groupIp6Addr->sin6_addr, sizeof(groupIp6Addr->sin6_addr)); + netty_unix_socket_setOption(env, fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq6, sizeof(mreq6)); + break; + default: + netty_unix_errors_throwIOException(env, "Address family not supported"); + break; + } +} + +static void netty_epoll_linuxsocket_leaveSsmGroup(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jbyteArray groupAddress, jbyteArray interfaceAddress, jint scopeId, jint interfaceIndex, jbyteArray sourceAddress) { + struct sockaddr_storage groupAddr; + socklen_t groupAddrSize; + struct sockaddr_storage interfaceAddr; + socklen_t interfaceAddrSize; + struct sockaddr_storage sourceAddr; + socklen_t sourceAddrSize; + struct sockaddr_in* groupIpAddr; + struct sockaddr_in* interfaceIpAddr; + struct sockaddr_in* sourceIpAddr; + + struct ip_mreq_source mreq; + struct group_source_req mreq6; + + memset(&groupAddr, 0, sizeof(groupAddr)); + memset(&sourceAddr, 0, sizeof(sourceAddr)); + memset(&interfaceAddr, 0, sizeof(interfaceAddr)); + + + if (netty_unix_socket_initSockaddr(env, ipv6, groupAddress, scopeId, 0, &groupAddr, &groupAddrSize) == -1) { + netty_unix_errors_throwIOException(env, "Could not init sockaddr for groupAddress"); + return; + } + + if (netty_unix_socket_initSockaddr(env, ipv6, sourceAddress, scopeId, 0, &sourceAddr, &sourceAddrSize) == -1) { + netty_unix_errors_throwIOException(env, "Could not init sockaddr for sourceAddress"); + return; + } + + switch (groupAddr.ss_family) { + case AF_INET: + if (netty_unix_socket_initSockaddr(env, ipv6, interfaceAddress, scopeId, 0, &interfaceAddr, &interfaceAddrSize) == -1) { + netty_unix_errors_throwIOException(env, "Could not init sockaddr for interfaceAddress"); + return; + } + interfaceIpAddr = (struct sockaddr_in*) &interfaceAddr; + + groupIpAddr = (struct sockaddr_in*) &groupAddr; + sourceIpAddr = (struct sockaddr_in*) &sourceAddr; + memcpy(&mreq.imr_multiaddr, &groupIpAddr->sin_addr, sizeof(groupIpAddr->sin_addr)); + memcpy(&mreq.imr_interface, &interfaceIpAddr->sin_addr, sizeof(interfaceIpAddr->sin_addr)); + memcpy(&mreq.imr_sourceaddr, &sourceIpAddr->sin_addr, sizeof(sourceIpAddr->sin_addr)); + netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP, &mreq, sizeof(mreq)); + break; + case AF_INET6: + if (interfaceIndex == -1) { + netty_unix_errors_throwIOException(env, "Unable to find network index"); + return; + } + + mreq6.gsr_group = groupAddr; + mreq6.gsr_interface = interfaceIndex; + mreq6.gsr_source = sourceAddr; + netty_unix_socket_setOption(env, fd, IPPROTO_IPV6, MCAST_LEAVE_SOURCE_GROUP, &mreq6, sizeof(mreq6)); + break; + default: + netty_unix_errors_throwIOException(env, "Address family not supported"); + break; + } +} + +static void netty_epoll_linuxsocket_setTcpMd5Sig(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jbyteArray address, jint scopeId, jbyteArray key) { + struct sockaddr_storage addr; + socklen_t addrSize; + + memset(&addr, 0, sizeof(addr)); + + if (netty_unix_socket_initSockaddr(env, ipv6, address, scopeId, 0, &addr, &addrSize) == -1) { + netty_unix_errors_throwIOException(env, "Could not init sockaddr"); + return; + } + + struct tcp_md5sig md5sig; + memset(&md5sig, 0, sizeof(md5sig)); + md5sig.tcpm_addr.ss_family = addr.ss_family; + + struct sockaddr_in* ipaddr; + struct sockaddr_in6* ip6addr; + + switch (addr.ss_family) { + case AF_INET: + ipaddr = (struct sockaddr_in*) &addr; + memcpy(&((struct sockaddr_in *) &md5sig.tcpm_addr)->sin_addr, &ipaddr->sin_addr, sizeof(ipaddr->sin_addr)); + break; + case AF_INET6: + ip6addr = (struct sockaddr_in6*) &addr; + memcpy(&((struct sockaddr_in6 *) &md5sig.tcpm_addr)->sin6_addr, &ip6addr->sin6_addr, sizeof(ip6addr->sin6_addr)); + break; + } + + if (key != NULL) { + md5sig.tcpm_keylen = (*env)->GetArrayLength(env, key); + (*env)->GetByteArrayRegion(env, key, 0, md5sig.tcpm_keylen, (void *) &md5sig.tcpm_key); + if ((*env)->ExceptionCheck(env) == JNI_TRUE) { + return; + } + } + + if (setsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &md5sig, sizeof(md5sig)) < 0) { + netty_unix_errors_throwIOExceptionErrorNo(env, "setsockopt() failed: ", errno); + } +} + +static int netty_epoll_linuxsocket_getInterface(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6) { + if (ipv6 == JNI_TRUE) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; + } else { + struct in_addr optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_IP, IP_MULTICAST_IF, &optval, sizeof(optval)) == -1) { + return -1; + } + + return ntohl(optval.s_addr); + } +} + +static jint netty_epoll_linuxsocket_getTimeToLive(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_IP, IP_TTL, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + + +static jint netty_epoll_linuxsocket_getIpMulticastLoop(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6) { + if (ipv6 == JNI_TRUE) { + u_int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &optval, sizeof(optval)) == -1) { + return -1; + } + return (jint) optval; + } else { + u_char optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_IP, IP_MULTICAST_LOOP, &optval, sizeof(optval)) == -1) { + return -1; + } + return (jint) optval; + } +} + +static jint netty_epoll_linuxsocket_getTcpKeepIdle(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_KEEPIDLE, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_getTcpKeepIntvl(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_getTcpKeepCnt(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_KEEPCNT, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_getTcpUserTimeout(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_isIpFreeBind(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_IP, IP_FREEBIND, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_isIpTransparent(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, SOL_IP, IP_TRANSPARENT, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_isIpRecvOrigDestAddr(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_IP, IP_RECVORIGDSTADDR, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static void netty_epoll_linuxsocket_getTcpInfo(JNIEnv* env, jclass clazz, jint fd, jlongArray array) { + struct tcp_info tcp_info; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_INFO, &tcp_info, sizeof(tcp_info)) == -1) { + return; + } + jlong cArray[32]; + // Expand to 64 bits, then cast away unsigned-ness. + cArray[0] = (jlong) (uint64_t) tcp_info.tcpi_state; + cArray[1] = (jlong) (uint64_t) tcp_info.tcpi_ca_state; + cArray[2] = (jlong) (uint64_t) tcp_info.tcpi_retransmits; + cArray[3] = (jlong) (uint64_t) tcp_info.tcpi_probes; + cArray[4] = (jlong) (uint64_t) tcp_info.tcpi_backoff; + cArray[5] = (jlong) (uint64_t) tcp_info.tcpi_options; + cArray[6] = (jlong) (uint64_t) tcp_info.tcpi_snd_wscale; + cArray[7] = (jlong) (uint64_t) tcp_info.tcpi_rcv_wscale; + cArray[8] = (jlong) (uint64_t) tcp_info.tcpi_rto; + cArray[9] = (jlong) (uint64_t) tcp_info.tcpi_ato; + cArray[10] = (jlong) (uint64_t) tcp_info.tcpi_snd_mss; + cArray[11] = (jlong) (uint64_t) tcp_info.tcpi_rcv_mss; + cArray[12] = (jlong) (uint64_t) tcp_info.tcpi_unacked; + cArray[13] = (jlong) (uint64_t) tcp_info.tcpi_sacked; + cArray[14] = (jlong) (uint64_t) tcp_info.tcpi_lost; + cArray[15] = (jlong) (uint64_t) tcp_info.tcpi_retrans; + cArray[16] = (jlong) (uint64_t) tcp_info.tcpi_fackets; + cArray[17] = (jlong) (uint64_t) tcp_info.tcpi_last_data_sent; + cArray[18] = (jlong) (uint64_t) tcp_info.tcpi_last_ack_sent; + cArray[19] = (jlong) (uint64_t) tcp_info.tcpi_last_data_recv; + cArray[20] = (jlong) (uint64_t) tcp_info.tcpi_last_ack_recv; + cArray[21] = (jlong) (uint64_t) tcp_info.tcpi_pmtu; + cArray[22] = (jlong) (uint64_t) tcp_info.tcpi_rcv_ssthresh; + cArray[23] = (jlong) (uint64_t) tcp_info.tcpi_rtt; + cArray[24] = (jlong) (uint64_t) tcp_info.tcpi_rttvar; + cArray[25] = (jlong) (uint64_t) tcp_info.tcpi_snd_ssthresh; + cArray[26] = (jlong) (uint64_t) tcp_info.tcpi_snd_cwnd; + cArray[27] = (jlong) (uint64_t) tcp_info.tcpi_advmss; + cArray[28] = (jlong) (uint64_t) tcp_info.tcpi_reordering; + cArray[29] = (jlong) (uint64_t) tcp_info.tcpi_rcv_rtt; + cArray[30] = (jlong) (uint64_t) tcp_info.tcpi_rcv_space; + cArray[31] = (jlong) (uint64_t) tcp_info.tcpi_total_retrans; + + (*env)->SetLongArrayRegion(env, array, 0, 32, cArray); +} + +static jint netty_epoll_linuxsocket_isTcpCork(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_CORK, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_getSoBusyPoll(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_BUSY_POLL, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_getTcpDeferAccept(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_isTcpQuickAck(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_QUICKACK, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_getTcpNotSentLowAt(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_NOTSENT_LOWAT, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jobject netty_epoll_linuxsocket_getPeerCredentials(JNIEnv *env, jclass clazz, jint fd) { + struct ucred credentials; + jclass peerCredentialsClass = NULL; + if(netty_unix_socket_getOption(env,fd, SOL_SOCKET, SO_PEERCRED, &credentials, sizeof (credentials)) == -1) { + return NULL; + } + jintArray gids = (*env)->NewIntArray(env, 1); + (*env)->SetIntArrayRegion(env, gids, 0, 1, (jint*) &credentials.gid); + + NETTY_JNI_UTIL_NEW_LOCAL_FROM_WEAK(env, peerCredentialsClass, peerCredentialsClassWeak, error); + + jobject creds = (*env)->NewObject(env, peerCredentialsClass, peerCredentialsMethodId, credentials.pid, credentials.uid, gids); + + NETTY_JNI_UTIL_DELETE_LOCAL(env, peerCredentialsClass); + return creds; + error: + return NULL; +} + +static jint netty_epoll_linuxsocket_isUdpGro(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, SOL_UDP, UDP_GRO, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static void netty_epoll_linuxsocket_setUdpGro(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, SOL_UDP, UDP_GRO, &optval, sizeof(optval)); +} + + +static jlong netty_epoll_linuxsocket_sendFile(JNIEnv* env, jclass clazz, jint fd, jobject fileRegion, jlong base_off, jlong off, jlong len) { + jobject fileChannel = (*env)->GetObjectField(env, fileRegion, fileChannelFieldId); + if (fileChannel == NULL) { + netty_unix_errors_throwRuntimeException(env, "failed to get DefaultFileRegion.file"); + return -1; + } + jobject fileDescriptor = (*env)->GetObjectField(env, fileChannel, fileDescriptorFieldId); + if (fileDescriptor == NULL) { + netty_unix_errors_throwRuntimeException(env, "failed to get FileChannelImpl.fd"); + return -1; + } + jint srcFd = (*env)->GetIntField(env, fileDescriptor, fdFieldId); + if (srcFd == -1) { + netty_unix_errors_throwRuntimeException(env, "failed to get FileDescriptor.fd"); + return -1; + } + ssize_t res; + off_t offset = base_off + off; + int err; + do { + res = sendfile(fd, srcFd, &offset, (size_t) len); + } while (res == -1 && ((err = errno) == EINTR)); + if (res < 0) { + return -err; + } + if (res > 0) { + // update the transferred field in DefaultFileRegion + (*env)->SetLongField(env, fileRegion, transferredFieldId, off + res); + } + + return res; +} + +// JNI Registered Methods End + +// JNI Method Registration Table Begin +static const JNINativeMethod fixed_method_table[] = { + { "newVSockStreamFd", "()I", (void *) netty_epoll_linuxsocket_newVSockStreamFd }, + { "bindVSock", "(III)I", (void *) netty_epoll_linuxsocket_bindVSock }, + { "connectVSock", "(III)I", (void *) netty_epoll_linuxsocket_connectVSock }, + { "remoteVSockAddress", "(I)[B", (void *) netty_epoll_linuxsocket_remoteVSockAddress }, + { "localVSockAddress", "(I)[B", (void *) netty_epoll_linuxsocket_localVSockAddress }, + { "setTimeToLive", "(II)V", (void *) netty_epoll_linuxsocket_setTimeToLive }, + { "getTimeToLive", "(I)I", (void *) netty_epoll_linuxsocket_getTimeToLive }, + { "setInterface", "(IZ[BII)V", (void *) netty_epoll_linuxsocket_setInterface }, + { "getInterface", "(IZ)I", (void *) netty_epoll_linuxsocket_getInterface }, + { "setIpMulticastLoop", "(IZI)V", (void * ) netty_epoll_linuxsocket_setIpMulticastLoop }, + { "getIpMulticastLoop", "(IZ)I", (void * ) netty_epoll_linuxsocket_getIpMulticastLoop }, + { "setTcpCork", "(II)V", (void *) netty_epoll_linuxsocket_setTcpCork }, + { "setSoBusyPoll", "(II)V", (void *) netty_epoll_linuxsocket_setSoBusyPoll }, + { "setTcpQuickAck", "(II)V", (void *) netty_epoll_linuxsocket_setTcpQuickAck }, + { "setTcpDeferAccept", "(II)V", (void *) netty_epoll_linuxsocket_setTcpDeferAccept }, + { "setTcpNotSentLowAt", "(II)V", (void *) netty_epoll_linuxsocket_setTcpNotSentLowAt }, + { "isTcpCork", "(I)I", (void *) netty_epoll_linuxsocket_isTcpCork }, + { "getSoBusyPoll", "(I)I", (void *) netty_epoll_linuxsocket_getSoBusyPoll }, + { "getTcpDeferAccept", "(I)I", (void *) netty_epoll_linuxsocket_getTcpDeferAccept }, + { "getTcpNotSentLowAt", "(I)I", (void *) netty_epoll_linuxsocket_getTcpNotSentLowAt }, + { "isTcpQuickAck", "(I)I", (void *) netty_epoll_linuxsocket_isTcpQuickAck }, + { "setTcpFastOpen", "(II)V", (void *) netty_epoll_linuxsocket_setTcpFastOpen }, + { "setTcpKeepIdle", "(II)V", (void *) netty_epoll_linuxsocket_setTcpKeepIdle }, + { "setTcpKeepIntvl", "(II)V", (void *) netty_epoll_linuxsocket_setTcpKeepIntvl }, + { "setTcpKeepCnt", "(II)V", (void *) netty_epoll_linuxsocket_setTcpKeepCnt }, + { "setTcpUserTimeout", "(II)V", (void *) netty_epoll_linuxsocket_setTcpUserTimeout }, + { "setIpFreeBind", "(II)V", (void *) netty_epoll_linuxsocket_setIpFreeBind }, + { "setIpTransparent", "(II)V", (void *) netty_epoll_linuxsocket_setIpTransparent }, + { "setIpRecvOrigDestAddr", "(II)V", (void *) netty_epoll_linuxsocket_setIpRecvOrigDestAddr }, + { "getTcpKeepIdle", "(I)I", (void *) netty_epoll_linuxsocket_getTcpKeepIdle }, + { "getTcpKeepIntvl", "(I)I", (void *) netty_epoll_linuxsocket_getTcpKeepIntvl }, + { "getTcpKeepCnt", "(I)I", (void *) netty_epoll_linuxsocket_getTcpKeepCnt }, + { "getTcpUserTimeout", "(I)I", (void *) netty_epoll_linuxsocket_getTcpUserTimeout }, + { "isIpFreeBind", "(I)I", (void *) netty_epoll_linuxsocket_isIpFreeBind }, + { "isIpTransparent", "(I)I", (void *) netty_epoll_linuxsocket_isIpTransparent }, + { "isIpRecvOrigDestAddr", "(I)I", (void *) netty_epoll_linuxsocket_isIpRecvOrigDestAddr }, + { "getTcpInfo", "(I[J)V", (void *) netty_epoll_linuxsocket_getTcpInfo }, + { "setTcpMd5Sig", "(IZ[BI[B)V", (void *) netty_epoll_linuxsocket_setTcpMd5Sig }, + { "joinGroup", "(IZ[B[BII)V", (void *) netty_epoll_linuxsocket_joinGroup }, + { "joinSsmGroup", "(IZ[B[BII[B)V", (void *) netty_epoll_linuxsocket_joinSsmGroup }, + { "leaveGroup", "(IZ[B[BII)V", (void *) netty_epoll_linuxsocket_leaveGroup }, + { "leaveSsmGroup", "(IZ[B[BII[B)V", (void *) netty_epoll_linuxsocket_leaveSsmGroup }, + { "isUdpGro", "(I)I", (void *) netty_epoll_linuxsocket_isUdpGro }, + { "setUdpGro", "(II)V", (void *) netty_epoll_linuxsocket_setUdpGro } + + // "sendFile" has a dynamic signature +}; + +static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]); + +static jint dynamicMethodsTableSize() { + return fixed_method_table_size + 2; // 2 is for the dynamic method signatures. +} + +static JNINativeMethod* createDynamicMethodsTable(const char* packagePrefix) { + char* dynamicTypeName = NULL; + size_t size = sizeof(JNINativeMethod) * dynamicMethodsTableSize(); + JNINativeMethod* dynamicMethods = malloc(size); + if (dynamicMethods == NULL) { + return NULL; + } + memset(dynamicMethods, 0, size); + memcpy(dynamicMethods, fixed_method_table, sizeof(fixed_method_table)); + + JNINativeMethod* dynamicMethod = &dynamicMethods[fixed_method_table_size]; + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/unix/PeerCredentials;", dynamicTypeName, error); + NETTY_JNI_UTIL_PREPEND("(I)L", dynamicTypeName, dynamicMethod->signature, error); + dynamicMethod->name = "getPeerCredentials"; + dynamicMethod->fnPtr = (void *) netty_epoll_linuxsocket_getPeerCredentials; + netty_jni_util_free_dynamic_name(&dynamicTypeName); + + ++dynamicMethod; + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/DefaultFileRegion;JJJ)J", dynamicTypeName, error); + NETTY_JNI_UTIL_PREPEND("(IL", dynamicTypeName, dynamicMethod->signature, error); + dynamicMethod->name = "sendFile"; + dynamicMethod->fnPtr = (void *) netty_epoll_linuxsocket_sendFile; + netty_jni_util_free_dynamic_name(&dynamicTypeName); + return dynamicMethods; +error: + free(dynamicTypeName); + netty_jni_util_free_dynamic_methods_table(dynamicMethods, fixed_method_table_size, dynamicMethodsTableSize()); + return NULL; +} + +// JNI Method Registration Table End + +// IMPORTANT: If you add any NETTY_JNI_UTIL_LOAD_CLASS or NETTY_JNI_UTIL_FIND_CLASS calls you also need to update +// Native to reflect that. +jint netty_epoll_linuxsocket_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { + int ret = JNI_ERR; + char* nettyClassName = NULL; + jclass fileRegionCls = NULL; + jclass fileChannelCls = NULL; + jclass fileDescriptorCls = NULL; + jclass peerCredentialsClass = NULL; + // Register the methods which are not referenced by static member variables + JNINativeMethod* dynamicMethods = createDynamicMethodsTable(packagePrefix); + if (dynamicMethods == NULL) { + goto done; + } + if (netty_jni_util_register_natives(env, + packagePrefix, + LINUXSOCKET_CLASSNAME, + dynamicMethods, + dynamicMethodsTableSize()) != 0) { + goto done; + } + + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/unix/PeerCredentials", nettyClassName, done); + + NETTY_JNI_UTIL_LOAD_CLASS_WEAK(env, peerCredentialsClassWeak, nettyClassName, done); + NETTY_JNI_UTIL_NEW_LOCAL_FROM_WEAK(env, peerCredentialsClass, peerCredentialsClassWeak, done); + + netty_jni_util_free_dynamic_name(&nettyClassName); + + NETTY_JNI_UTIL_GET_METHOD(env, peerCredentialsClass, peerCredentialsMethodId, "", "(II[I)V", done); + + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/DefaultFileRegion", nettyClassName, done); + NETTY_JNI_UTIL_FIND_CLASS(env, fileRegionCls, nettyClassName, done); + netty_jni_util_free_dynamic_name(&nettyClassName); + + NETTY_JNI_UTIL_GET_FIELD(env, fileRegionCls, fileChannelFieldId, "file", "Ljava/nio/channels/FileChannel;", done); + NETTY_JNI_UTIL_GET_FIELD(env, fileRegionCls, transferredFieldId, "transferred", "J", done); + + NETTY_JNI_UTIL_FIND_CLASS(env, fileChannelCls, "sun/nio/ch/FileChannelImpl", done); + NETTY_JNI_UTIL_GET_FIELD(env, fileChannelCls, fileDescriptorFieldId, "fd", "Ljava/io/FileDescriptor;", done); + + NETTY_JNI_UTIL_FIND_CLASS(env, fileDescriptorCls, "java/io/FileDescriptor", done); + NETTY_JNI_UTIL_TRY_GET_FIELD(env, fileDescriptorCls, fdFieldId, "fd", "I"); + if (fdFieldId == NULL) { + // Android uses a different field name, let's try it. + NETTY_JNI_UTIL_GET_FIELD(env, fileDescriptorCls, fdFieldId, "descriptor", "I", done); + } + ret = NETTY_JNI_UTIL_JNI_VERSION; +done: + netty_jni_util_free_dynamic_methods_table(dynamicMethods, fixed_method_table_size, dynamicMethodsTableSize()); + free(nettyClassName); + + NETTY_JNI_UTIL_DELETE_LOCAL(env, peerCredentialsClass); + return ret; +} + +void netty_epoll_linuxsocket_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix) { + NETTY_JNI_UTIL_UNLOAD_CLASS_WEAK(env, peerCredentialsClassWeak); + + netty_jni_util_unregister_natives(env, packagePrefix, LINUXSOCKET_CLASSNAME); +} diff --git a/netty-channel-epoll-native/src/main/c/netty_epoll_linuxsocket.h b/netty-channel-epoll-native/src/main/c/netty_epoll_linuxsocket.h new file mode 100644 index 0000000..b4cb301 --- /dev/null +++ b/netty-channel-epoll-native/src/main/c/netty_epoll_linuxsocket.h @@ -0,0 +1,26 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#ifndef NETTY_EPOLL_LINUXSOCKET_H_ +#define NETTY_EPOLL_LINUXSOCKET_H_ + +#include + +// JNI initialization hooks. Users of this file are responsible for calling these in the JNI_OnLoad and JNI_OnUnload methods. +jint netty_epoll_linuxsocket_JNI_OnLoad(JNIEnv* env, const char* packagePrefix); +void netty_epoll_linuxsocket_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix); + +#endif diff --git a/netty-channel-epoll-native/src/main/c/netty_epoll_native.c b/netty-channel-epoll-native/src/main/c/netty_epoll_native.c new file mode 100644 index 0000000..db23c3a --- /dev/null +++ b/netty-channel-epoll-native/src/main/c/netty_epoll_native.c @@ -0,0 +1,919 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// Needed to be able to use syscalls directly and so not depend on newer GLIBC versions +#include +#include + +// Needed for UDP_SEGMENT +#include + +#include "netty_epoll_linuxsocket.h" +#include "netty_unix_buffer.h" +#include "netty_unix_errors.h" +#include "netty_unix_filedescriptor.h" +#include "netty_unix_jni.h" +#include "netty_unix_limits.h" +#include "netty_unix_socket.h" +#include "netty_unix_util.h" +#include "netty_unix.h" + +// Add define if NETTY_BUILD_STATIC is defined so it is picked up in netty_jni_util.c +#ifdef NETTY_BUILD_STATIC +#define NETTY_JNI_UTIL_BUILD_STATIC +#endif + +#define STATICALLY_CLASSNAME "io/netty/channel/epoll/NativeStaticallyReferencedJniMethods" +#define NATIVE_CLASSNAME "io/netty/channel/epoll/Native" + +// TCP_FASTOPEN is defined in linux 3.7. We define this here so older kernels can compile. +#ifndef TCP_FASTOPEN +#define TCP_FASTOPEN 23 +#endif + +// Allow to compile on systems with older kernels. +#ifndef UDP_SEGMENT +#define UDP_SEGMENT 103 +#endif + +// UDP_GRO is defined in linux 5. We define this here so older kernels can compile. +#ifndef UDP_GRO +#define UDP_GRO 104 +#endif + +#ifdef IP_RECVORIGDSTADDR +#if !defined(SOL_IP) && defined(IPPROTO_IP) +#define SOL_IP IPPROTO_IP +#endif /* !SOL_IP && IPPROTO_IP */ +#endif // IP_RECVORIGDSTADDR + +// optional +extern int epoll_create1(int flags) __attribute__((weak)); +extern int epoll_pwait2(int epfd, struct epoll_event *events, int maxevents, const struct timespec *timeout, const sigset_t *sigmask) __attribute__((weak)); + +#ifndef __USE_GNU +struct mmsghdr { + struct msghdr msg_hdr; /* Message header */ + unsigned int msg_len; /* Number of bytes transmitted */ +}; +#endif + +// All linux syscall numbers are stable so this is safe. +#ifndef SYS_recvmmsg +// Only support SYS_recvmmsg for __x86_64__ / __i386__ for now +#if defined(__x86_64__) +// See https://github.com/torvalds/linux/blob/v5.4/arch/x86/entry/syscalls/syscall_64.tbl +#define SYS_recvmmsg 299 +#elif defined(__i386__) +// See https://github.com/torvalds/linux/blob/v5.4/arch/x86/entry/syscalls/syscall_32.tbl +#define SYS_recvmmsg 337 +#elif defined(__loongarch64) +// See https://github.com/torvalds/linux/blob/v6.1/include/uapi/asm-generic/unistd.h +#define SYS_recvmmsg 243 +#else +#define SYS_recvmmsg -1 +#endif +#endif // SYS_recvmmsg + +#ifndef SYS_sendmmsg +// Only support SYS_sendmmsg for __x86_64__ / __i386__ for now +#if defined(__x86_64__) +// See https://github.com/torvalds/linux/blob/v5.4/arch/x86/entry/syscalls/syscall_64.tbl +#define SYS_sendmmsg 307 +#elif defined(__i386__) +// See https://github.com/torvalds/linux/blob/v5.4/arch/x86/entry/syscalls/syscall_32.tbl +#define SYS_sendmmsg 345 +#elif defined(__loongarch64) +// See https://github.com/torvalds/linux/blob/v6.1/include/uapi/asm-generic/unistd.h +#define SYS_sendmmsg 269 +#else +#define SYS_sendmmsg -1 +#endif +#endif // SYS_sendmmsg + +// Those are initialized in the init(...) method and cached for performance reasons +static jfieldID packetSenderAddrFieldId = NULL; +static jfieldID packetSenderAddrLenFieldId = NULL; +static jfieldID packetSenderScopeIdFieldId = NULL; +static jfieldID packetSenderPortFieldId = NULL; +static jfieldID packetRecipientAddrFieldId = NULL; +static jfieldID packetRecipientAddrLenFieldId = NULL; +static jfieldID packetRecipientScopeIdFieldId = NULL; +static jfieldID packetRecipientPortFieldId = NULL; + +static jfieldID packetSegmentSizeFieldId = NULL; +static jfieldID packetMemoryAddressFieldId = NULL; +static jfieldID packetCountFieldId = NULL; + +static const char* staticPackagePrefix = NULL; +static int register_unix_called = 0; +static int epoll_pwait2_supported = 0; + +// util methods +static int getSysctlValue(const char * property, int* returnValue) { + int rc = -1; + FILE *fd=fopen(property, "r"); + if (fd != NULL) { + char buf[32] = {0x0}; + if (fgets(buf, 32, fd) != NULL) { + *returnValue = atoi(buf); + rc = 0; + } + fclose(fd); + } + return rc; +} + +static inline jint epollCtl(JNIEnv* env, jint efd, int op, jint fd, jint flags) { + uint32_t events = flags; + struct epoll_event ev = { + .data.fd = fd, + .events = events + }; + + return epoll_ctl(efd, op, fd, &ev); +} +// JNI Registered Methods Begin +static jint netty_epoll_native_eventFd(JNIEnv* env, jclass clazz) { + jint eventFD = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + + if (eventFD < 0) { + netty_unix_errors_throwChannelExceptionErrorNo(env, "eventfd() failed: ", errno); + } + return eventFD; +} + +static jint netty_epoll_native_timerFd(JNIEnv* env, jclass clazz) { + jint timerFD = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); + + if (timerFD < 0) { + netty_unix_errors_throwChannelExceptionErrorNo(env, "timerfd_create() failed: ", errno); + } + return timerFD; +} + +static void netty_epoll_native_eventFdWrite(JNIEnv* env, jclass clazz, jint fd, jlong value) { + uint64_t val; + + for (;;) { + jint ret = eventfd_write(fd, (eventfd_t) value); + + if (ret < 0) { + // We need to read before we can write again, let's try to read and then write again and if this + // fails we will bail out. + // + // See https://man7.org/linux/man-pages/man2/eventfd.2.html. + if (errno == EAGAIN) { + if (eventfd_read(fd, &val) == 0 || errno == EAGAIN) { + // Try again + continue; + } + netty_unix_errors_throwChannelExceptionErrorNo(env, "eventfd_read(...) failed: ", errno); + } else { + netty_unix_errors_throwChannelExceptionErrorNo(env, "eventfd_write(...) failed: ", errno); + } + } + break; + } +} + +static void netty_epoll_native_eventFdRead(JNIEnv* env, jclass clazz, jint fd) { + uint64_t eventfd_t; + + if (eventfd_read(fd, &eventfd_t) != 0) { + // something is serious wrong + netty_unix_errors_throwRuntimeException(env, "eventfd_read() failed"); + } +} + +static jint netty_epoll_native_epollCreate(JNIEnv* env, jclass clazz) { + jint efd; + if (epoll_create1) { + efd = epoll_create1(EPOLL_CLOEXEC); + } else { + // size will be ignored anyway but must be positive + efd = epoll_create(126); + } + if (efd < 0) { + int err = errno; + if (epoll_create1) { + netty_unix_errors_throwChannelExceptionErrorNo(env, "epoll_create1() failed: ", err); + } else { + netty_unix_errors_throwChannelExceptionErrorNo(env, "epoll_create() failed: ", err); + } + return efd; + } + if (!epoll_create1) { + if (fcntl(efd, F_SETFD, FD_CLOEXEC) < 0) { + int err = errno; + close(efd); + netty_unix_errors_throwChannelExceptionErrorNo(env, "fcntl() failed: ", err); + return err; + } + } + return efd; +} + +static jint netty_epoll_native_epollWait(JNIEnv* env, jclass clazz, jint efd, jlong address, jint len, jint timeout) { + struct epoll_event *ev = (struct epoll_event*) (intptr_t) address; + int result, err; + + do { + result = epoll_wait(efd, ev, len, timeout); + if (result >= 0) { + return result; + } + } while((err = errno) == EINTR); + return -err; +} + +// This needs to be consistent with Native.java +#define EPOLL_WAIT_RESULT(V, ARM_TIMER) ((jlong) ((uint64_t) ((uint32_t) V) << 32 | ARM_TIMER)) + +static jlong netty_epoll_native_epollWait0(JNIEnv* env, jclass clazz, jint efd, jlong address, jint len, jint timerFd, jint tvSec, jint tvNsec, jlong millisThreshold) { + // only reschedule the timer if there is a newer event. + // -1 is a special value used by EpollEventLoop. + uint32_t armTimer = millisThreshold <= 0 ? 1 : 0; + if (tvSec != ((jint) -1) && tvNsec != ((jint) -1)) { + if (millisThreshold > 0 && (tvSec != 0 || tvNsec != 0)) { + // Let's try to reduce the syscalls as much as possible as timerfd_settime(...) can be expensive: + // See https://github.com/netty/netty/issues/11695 + + if (epoll_pwait2_supported == 1) { + // We have epoll_pwait2(...) and it is supported, this means we can just pass in the itimerspec directly and not need an + // extra syscall even for very small timeouts. + struct timespec ts = { tvSec, tvNsec }; + struct epoll_event *ev = (struct epoll_event*) (intptr_t) address; + int result, err; + do { + result = epoll_pwait2(efd, ev, len, &ts, NULL); + if (result >= 0) { + return EPOLL_WAIT_RESULT(result, armTimer); + } + } while((err = errno) == EINTR); + return EPOLL_WAIT_RESULT(-err, armTimer); + } + + int millis = tvNsec / 1000000; + // Check if we can reduce the syscall overhead by just use epoll_wait. This is done in cases when we can + // tolerate some "drift". + if (tvNsec == 0 || + // Let's use the threshold to accept that we may be not 100 % accurate and ignore anything that + // is smaller then 1 ms. + millis >= millisThreshold || + tvSec > 0) { + millis += tvSec * 1000; + int result = netty_epoll_native_epollWait(env, clazz, efd, address, len, millis); + return EPOLL_WAIT_RESULT(result, armTimer); + } + } + struct itimerspec ts; + memset(&ts.it_interval, 0, sizeof(struct timespec)); + ts.it_value.tv_sec = tvSec; + ts.it_value.tv_nsec = tvNsec; + if (timerfd_settime(timerFd, 0, &ts, NULL) < 0) { + netty_unix_errors_throwChannelExceptionErrorNo(env, "timerfd_settime() failed: ", errno); + return -1; + } + armTimer = 1; + } + int result = netty_epoll_native_epollWait(env, clazz, efd, address, len, -1); + return EPOLL_WAIT_RESULT(result, armTimer); +} + +static inline void cpu_relax() { +#if defined(__x86_64__) + asm volatile("pause\n": : :"memory"); +#elif defined(__aarch64__) + asm volatile("isb\n": : :"memory"); +#elif defined(__riscv) && defined(__riscv_zihintpause) + asm volatile("pause\n": : :"memory"); +#endif +} + +static jint netty_epoll_native_epollBusyWait0(JNIEnv* env, jclass clazz, jint efd, jlong address, jint len) { + struct epoll_event *ev = (struct epoll_event*) (intptr_t) address; + int result, err; + + // Zeros = poll (aka return immediately). + do { + result = epoll_wait(efd, ev, len, 0); + if (result == 0) { + // Since we're always polling epoll_wait with no timeout, + // signal CPU that we're in a busy loop + cpu_relax(); + } + + if (result >= 0) { + return result; + } + } while((err = errno) == EINTR); + + return -err; +} + +static jint netty_epoll_native_epollCtlAdd0(JNIEnv* env, jclass clazz, jint efd, jint fd, jint flags) { + int res = epollCtl(env, efd, EPOLL_CTL_ADD, fd, flags); + if (res < 0) { + return -errno; + } + return res; +} +static jint netty_epoll_native_epollCtlMod0(JNIEnv* env, jclass clazz, jint efd, jint fd, jint flags) { + int res = epollCtl(env, efd, EPOLL_CTL_MOD, fd, flags); + if (res < 0) { + return -errno; + } + return res; +} + +static jint netty_epoll_native_epollCtlDel0(JNIEnv* env, jclass clazz, jint efd, jint fd) { + // Create an empty event to workaround a bug in older kernels which can not handle NULL. + struct epoll_event event = { 0 }; + int res = epoll_ctl(efd, EPOLL_CTL_DEL, fd, &event); + if (res < 0) { + return -errno; + } + return res; +} + +static jint netty_epoll_native_sendmmsg0(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jobjectArray packets, jint offset, jint len) { + struct mmsghdr msg[len]; + struct sockaddr_storage addr[len]; + char controls[len][CMSG_SPACE(sizeof(uint16_t))]; + + socklen_t addrSize; + int i; + + memset(msg, 0, sizeof(msg)); + + for (i = 0; i < len; i++) { + jobject packet = (*env)->GetObjectArrayElement(env, packets, i + offset); + jbyteArray address = (jbyteArray) (*env)->GetObjectField(env, packet, packetRecipientAddrFieldId); + jint addrLen = (*env)->GetIntField(env, packet, packetRecipientAddrLenFieldId); + jint packetSegmentSize = (*env)->GetIntField(env, packet, packetSegmentSizeFieldId); + if (packetSegmentSize > 0) { + msg[i].msg_hdr.msg_control = controls[i]; + msg[i].msg_hdr.msg_controllen = sizeof(controls[i]); + struct cmsghdr *cm = CMSG_FIRSTHDR(&msg[i].msg_hdr); + cm->cmsg_level = SOL_UDP; + cm->cmsg_type = UDP_SEGMENT; + cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + *((uint16_t *) CMSG_DATA(cm)) = packetSegmentSize; + } + if (addrLen != 0) { + jint scopeId = (*env)->GetIntField(env, packet, packetRecipientScopeIdFieldId); + jint port = (*env)->GetIntField(env, packet, packetRecipientPortFieldId); + + if (netty_unix_socket_initSockaddr(env, ipv6, address, scopeId, port, &addr[i], &addrSize) == -1) { + return -1; + } + msg[i].msg_hdr.msg_name = &addr[i]; + msg[i].msg_hdr.msg_namelen = addrSize; + } + + msg[i].msg_hdr.msg_iov = (struct iovec*) (intptr_t) (*env)->GetLongField(env, packet, packetMemoryAddressFieldId); + msg[i].msg_hdr.msg_iovlen = (*env)->GetIntField(env, packet, packetCountFieldId); + } + + ssize_t res; + int err; + do { + // We directly use the syscall to prevent depending on GLIBC 2.14. + res = syscall(SYS_sendmmsg, fd, msg, len, 0); + // keep on writing if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + return -err; + } + return (jint) res; +} + +static void init_packet_address(JNIEnv* env, jobject packet, struct sockaddr_storage* addr, jfieldID addrFieldId, + jfieldID addrLenFieldId, jfieldID scopeIdFieldId, jfieldID portFieldId) { + jbyteArray address = (jbyteArray) (*env)->GetObjectField(env, packet, addrFieldId); + + if (addr->ss_family == AF_INET) { + struct sockaddr_in* ipaddr = (struct sockaddr_in*) addr; + + (*env)->SetByteArrayRegion(env, address, 0, 4, (jbyte*) &ipaddr->sin_addr.s_addr); + (*env)->SetIntField(env, packet, addrLenFieldId, 4); + (*env)->SetIntField(env, packet, scopeIdFieldId, 0); + (*env)->SetIntField(env, packet, portFieldId, ntohs(ipaddr->sin_port)); + } else { + int addrLen = netty_unix_socket_ipAddressLength(addr); + struct sockaddr_in6* ip6addr = (struct sockaddr_in6*) addr; + + if (addrLen == 4) { + // IPV4 mapped IPV6 address + jbyte* addr = (jbyte*) &ip6addr->sin6_addr.s6_addr; + (*env)->SetByteArrayRegion(env, address, 0, 4, addr + 12); + } else { + (*env)->SetByteArrayRegion(env, address, 0, 16, (jbyte*) &ip6addr->sin6_addr.s6_addr); + } + (*env)->SetIntField(env, packet, addrLenFieldId, addrLen); + (*env)->SetIntField(env, packet, scopeIdFieldId, ip6addr->sin6_scope_id); + (*env)->SetIntField(env, packet, portFieldId, ntohs(ip6addr->sin6_port)); + } +} + +static void init_packet(JNIEnv* env, jobject packet, struct msghdr* msg, int len) { + (*env)->SetIntField(env, packet, packetCountFieldId, len); + + init_packet_address(env, packet, (struct sockaddr_storage*) msg->msg_name, packetSenderAddrFieldId, packetSenderAddrLenFieldId, packetSenderScopeIdFieldId, packetSenderPortFieldId); + + struct cmsghdr *cmsg = NULL; + uint16_t gso_size = 0; + uint16_t *gsosizeptr = NULL; + for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) { + gsosizeptr = (uint16_t *) CMSG_DATA(cmsg); + gso_size = *gsosizeptr; + } +#ifdef IP_RECVORIGDSTADDR + else if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVORIGDSTADDR) { + init_packet_address(env, packet, (struct sockaddr_storage*) CMSG_DATA(cmsg), packetRecipientAddrFieldId, packetRecipientAddrLenFieldId, packetRecipientScopeIdFieldId, packetRecipientPortFieldId); + } +#endif // IP_RECVORIGDSTADDR + } + (*env)->SetIntField(env, packet, packetSegmentSizeFieldId, gso_size); +} + +static jint netty_epoll_native_recvmsg0(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jobject packet) { + struct msghdr msg = { 0 }; + struct sockaddr_storage sock_address; + int addrSize = sizeof(sock_address); + // Enough space for GRO and IP_RECVORIGDSTADDR + char control[CMSG_SPACE(sizeof(uint16_t)) + sizeof(struct sockaddr_storage)] = { 0 }; + msg.msg_name = &sock_address; + msg.msg_namelen = (socklen_t) addrSize; + msg.msg_iov = (struct iovec*) (intptr_t) (*env)->GetLongField(env, packet, packetMemoryAddressFieldId); + msg.msg_iovlen = (*env)->GetIntField(env, packet, packetCountFieldId); + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + ssize_t res; + int err; + do { + res = recvmsg(fd, &msg, 0); + // keep on reading if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + if (res < 0) { + return -err; + } + init_packet(env, packet, &msg, res); + return (jint) res; +} + +static jint netty_epoll_native_recvmmsg0(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jobjectArray packets, jint offset, jint len) { + struct mmsghdr msg[len]; + memset(msg, 0, sizeof(msg)); + struct sockaddr_storage addr[len]; + int addrSize = sizeof(addr); + memset(addr, 0, addrSize); + int storageSize = sizeof(struct sockaddr_storage); + char* cntrlbuf = NULL; + +#ifdef IP_RECVORIGDSTADDR + int readLocalAddr = 0; + if (netty_unix_socket_getOption(env, fd, IPPROTO_IP, IP_RECVORIGDSTADDR, + &readLocalAddr, sizeof(readLocalAddr)) < 0) { + cntrlbuf = malloc(sizeof(char) * storageSize * len); + } +#endif // IP_RECVORIGDSTADDR + + int i; + + for (i = 0; i < len; i++) { + jobject packet = (*env)->GetObjectArrayElement(env, packets, i + offset); + msg[i].msg_hdr.msg_iov = (struct iovec*) (intptr_t) (*env)->GetLongField(env, packet, packetMemoryAddressFieldId); + msg[i].msg_hdr.msg_iovlen = (*env)->GetIntField(env, packet, packetCountFieldId); + + msg[i].msg_hdr.msg_name = addr + i; + msg[i].msg_hdr.msg_namelen = (socklen_t) addrSize; + + if (cntrlbuf != NULL) { + msg[i].msg_hdr.msg_control = cntrlbuf + i * storageSize; + msg[i].msg_hdr.msg_controllen = storageSize; + } + } + + ssize_t res; + int err; + do { + // We directly use the syscall to prevent depending on GLIBC 2.12. + res = syscall(SYS_recvmmsg, fd, &msg, len, 0, NULL); + + // keep on reading if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res >= 0) { + for (i = 0; i < res; i++) { + jobject packet = (*env)->GetObjectArrayElement(env, packets, i + offset); + init_packet(env, packet, &msg[i].msg_hdr, msg[i].msg_len); + } + } + // Free the control message buffer if needed. + free(cntrlbuf); + + if (res < 0) { + return -err; + } + return (jint) res; +} + +static jstring netty_epoll_native_kernelVersion(JNIEnv* env, jclass clazz) { + struct utsname name; + + int res = uname(&name); + if (res == 0) { + return (*env)->NewStringUTF(env, name.release); + } + netty_unix_errors_throwRuntimeExceptionErrorNo(env, "uname() failed: ", errno); + return NULL; +} + +static jboolean netty_epoll_native_isSupportingSendmmsg(JNIEnv* env, jclass clazz) { + if (SYS_sendmmsg == -1) { + return JNI_FALSE; + } + if (syscall(SYS_sendmmsg, -1, NULL, 0, 0) == -1) { + if (errno == ENOSYS) { + return JNI_FALSE; + } + } + return JNI_TRUE; +} + +static jboolean netty_epoll_native_isSupportingUdpSegment(JNIEnv* env, jclass clazz) { + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + return JNI_FALSE; + } + int gso_size = 512; + int ret = setsockopt(fd, SOL_UDP, UDP_SEGMENT, &gso_size, sizeof(gso_size)); + close(fd); + return ret == -1 ? JNI_FALSE : JNI_TRUE; +} + +static jboolean netty_epoll_native_isSupportingRecvmmsg(JNIEnv* env, jclass clazz) { + if (SYS_recvmmsg == -1) { + return JNI_FALSE; + } + if (syscall(SYS_recvmmsg, -1, NULL, 0, 0, NULL) == -1) { + if (errno == ENOSYS) { + return JNI_FALSE; + } + } + return JNI_TRUE; +} + +static jint netty_epoll_native_tcpFastopenMode(JNIEnv* env, jclass clazz) { + int fastopen = 0; + getSysctlValue("/proc/sys/net/ipv4/tcp_fastopen", &fastopen); + return fastopen; +} + +static jint netty_epoll_native_epollet(JNIEnv* env, jclass clazz) { + return EPOLLET; +} + +static jint netty_epoll_native_epollin(JNIEnv* env, jclass clazz) { + return EPOLLIN; +} + +static jint netty_epoll_native_epollout(JNIEnv* env, jclass clazz) { + return EPOLLOUT; +} + +static jint netty_epoll_native_epollrdhup(JNIEnv* env, jclass clazz) { + return EPOLLRDHUP; +} + +static jint netty_epoll_native_epollerr(JNIEnv* env, jclass clazz) { + return EPOLLERR; +} + +static jint netty_epoll_native_sizeofEpollEvent(JNIEnv* env, jclass clazz) { + return sizeof(struct epoll_event); +} + +static jint netty_epoll_native_offsetofEpollData(JNIEnv* env, jclass clazz) { + return offsetof(struct epoll_event, data); +} + +static jint netty_epoll_native_splice0(JNIEnv* env, jclass clazz, jint fd, jlong offIn, jint fdOut, jlong offOut, jlong len) { + ssize_t res; + int err; + loff_t off_in = (loff_t) offIn; + loff_t off_out = (loff_t) offOut; + + loff_t* p_off_in = off_in >= 0 ? &off_in : NULL; + loff_t* p_off_out = off_out >= 0 ? &off_out : NULL; + + do { + res = splice(fd, p_off_in, fdOut, p_off_out, (size_t) len, SPLICE_F_NONBLOCK | SPLICE_F_MOVE); + // keep on splicing if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + return -err; + } + return (jint) res; +} + +static jint netty_epoll_native_tcpMd5SigMaxKeyLen(JNIEnv* env, jclass clazz) { + struct tcp_md5sig md5sig; + + // Defensive size check + if (sizeof(md5sig.tcpm_key) < TCP_MD5SIG_MAXKEYLEN) { + return sizeof(md5sig.tcpm_key); + } + + return TCP_MD5SIG_MAXKEYLEN; +} + +static jint netty_epoll_native_registerUnix(JNIEnv* env, jclass clazz) { + register_unix_called = 1; + return netty_unix_register(env, staticPackagePrefix); +} + +// JNI Registered Methods End + +// JNI Method Registration Table Begin +static const JNINativeMethod statically_referenced_fixed_method_table[] = { + { "epollet", "()I", (void *) netty_epoll_native_epollet }, + { "epollin", "()I", (void *) netty_epoll_native_epollin }, + { "epollout", "()I", (void *) netty_epoll_native_epollout }, + { "epollrdhup", "()I", (void *) netty_epoll_native_epollrdhup }, + { "epollerr", "()I", (void *) netty_epoll_native_epollerr }, + { "tcpMd5SigMaxKeyLen", "()I", (void *) netty_epoll_native_tcpMd5SigMaxKeyLen }, + { "isSupportingSendmmsg", "()Z", (void *) netty_epoll_native_isSupportingSendmmsg }, + { "isSupportingRecvmmsg", "()Z", (void *) netty_epoll_native_isSupportingRecvmmsg }, + { "tcpFastopenMode", "()I", (void *) netty_epoll_native_tcpFastopenMode }, + { "kernelVersion", "()Ljava/lang/String;", (void *) netty_epoll_native_kernelVersion } +}; +static const jint statically_referenced_fixed_method_table_size = sizeof(statically_referenced_fixed_method_table) / sizeof(statically_referenced_fixed_method_table[0]); +static const JNINativeMethod fixed_method_table[] = { + { "eventFd", "()I", (void *) netty_epoll_native_eventFd }, + { "timerFd", "()I", (void *) netty_epoll_native_timerFd }, + { "eventFdWrite", "(IJ)V", (void *) netty_epoll_native_eventFdWrite }, + { "eventFdRead", "(I)V", (void *) netty_epoll_native_eventFdRead }, + { "epollCreate", "()I", (void *) netty_epoll_native_epollCreate }, + { "epollWait0", "(IJIIIIJ)J", (void *) netty_epoll_native_epollWait0 }, + { "epollWait", "(IJII)I", (void *) netty_epoll_native_epollWait }, + { "epollBusyWait0", "(IJI)I", (void *) netty_epoll_native_epollBusyWait0 }, + { "epollCtlAdd0", "(III)I", (void *) netty_epoll_native_epollCtlAdd0 }, + { "epollCtlMod0", "(III)I", (void *) netty_epoll_native_epollCtlMod0 }, + { "epollCtlDel0", "(II)I", (void *) netty_epoll_native_epollCtlDel0 }, + // "sendmmsg0" has a dynamic signature + { "sizeofEpollEvent", "()I", (void *) netty_epoll_native_sizeofEpollEvent }, + { "offsetofEpollData", "()I", (void *) netty_epoll_native_offsetofEpollData }, + { "splice0", "(IJIJJ)I", (void *) netty_epoll_native_splice0 }, + { "isSupportingUdpSegment", "()Z", (void *) netty_epoll_native_isSupportingUdpSegment }, + { "registerUnix", "()I", (void *) netty_epoll_native_registerUnix }, + +}; +static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]); + +static jint dynamicMethodsTableSize() { + return fixed_method_table_size + 3; // 3 is for the dynamic method signatures. +} + +static JNINativeMethod* createDynamicMethodsTable(const char* packagePrefix) { + char* dynamicTypeName = NULL; + size_t size = sizeof(JNINativeMethod) * dynamicMethodsTableSize(); + JNINativeMethod* dynamicMethods = malloc(size); + if (dynamicMethods == NULL) { + return NULL; + } + memset(dynamicMethods, 0, size); + memcpy(dynamicMethods, fixed_method_table, sizeof(fixed_method_table)); + + JNINativeMethod* dynamicMethod = &dynamicMethods[fixed_method_table_size]; + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/epoll/NativeDatagramPacketArray$NativeDatagramPacket;II)I", dynamicTypeName, error); + NETTY_JNI_UTIL_PREPEND("(IZ[L", dynamicTypeName, dynamicMethod->signature, error); + dynamicMethod->name = "sendmmsg0"; + dynamicMethod->fnPtr = (void *) netty_epoll_native_sendmmsg0; + netty_jni_util_free_dynamic_name(&dynamicTypeName); + + ++dynamicMethod; + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/epoll/NativeDatagramPacketArray$NativeDatagramPacket;II)I", dynamicTypeName, error); + NETTY_JNI_UTIL_PREPEND("(IZ[L", dynamicTypeName, dynamicMethod->signature, error); + dynamicMethod->name = "recvmmsg0"; + dynamicMethod->fnPtr = (void *) netty_epoll_native_recvmmsg0; + netty_jni_util_free_dynamic_name(&dynamicTypeName); + + ++dynamicMethod; + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/epoll/NativeDatagramPacketArray$NativeDatagramPacket;)I", dynamicTypeName, error); + NETTY_JNI_UTIL_PREPEND("(IZL", dynamicTypeName, dynamicMethod->signature, error); + dynamicMethod->name = "recvmsg0"; + dynamicMethod->fnPtr = (void *) netty_epoll_native_recvmsg0; + netty_jni_util_free_dynamic_name(&dynamicTypeName); + + return dynamicMethods; +error: + free(dynamicTypeName); + netty_jni_util_free_dynamic_methods_table(dynamicMethods, fixed_method_table_size, dynamicMethodsTableSize()); + return NULL; +} + +// JNI Method Registration Table End + +// IMPORTANT: If you add any NETTY_JNI_UTIL_LOAD_CLASS or NETTY_JNI_UTIL_FIND_CLASS calls you also need to update +// Native to reflect that. +static jint netty_epoll_native_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { + int ret = JNI_ERR; + int staticallyRegistered = 0; + int nativeRegistered = 0; + int linuxsocketOnLoadCalled = 0; + char* nettyClassName = NULL; + jclass nativeDatagramPacketCls = NULL; + JNINativeMethod* dynamicMethods = NULL; + + // We must register the statically referenced methods first! + if (netty_jni_util_register_natives(env, + packagePrefix, + STATICALLY_CLASSNAME, + statically_referenced_fixed_method_table, + statically_referenced_fixed_method_table_size) != 0) { + goto done; + } + staticallyRegistered = 1; + + // Register the methods which are not referenced by static member variables + dynamicMethods = createDynamicMethodsTable(packagePrefix); + if (dynamicMethods == NULL) { + goto done; + } + + if (netty_jni_util_register_natives(env, + packagePrefix, + NATIVE_CLASSNAME, + dynamicMethods, + dynamicMethodsTableSize()) != 0) { + goto done; + } + nativeRegistered = 1; + + if (netty_epoll_linuxsocket_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + goto done; + } + linuxsocketOnLoadCalled = 1; + + // Initialize this module + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/epoll/NativeDatagramPacketArray$NativeDatagramPacket", nettyClassName, done); + NETTY_JNI_UTIL_FIND_CLASS(env, nativeDatagramPacketCls, nettyClassName, done); + netty_jni_util_free_dynamic_name(&nettyClassName); + + NETTY_JNI_UTIL_GET_FIELD(env, nativeDatagramPacketCls, packetSenderAddrFieldId, "senderAddr", "[B", done); + NETTY_JNI_UTIL_GET_FIELD(env, nativeDatagramPacketCls, packetSenderAddrLenFieldId, "senderAddrLen", "I", done); + NETTY_JNI_UTIL_GET_FIELD(env, nativeDatagramPacketCls, packetSenderScopeIdFieldId, "senderScopeId", "I", done); + NETTY_JNI_UTIL_GET_FIELD(env, nativeDatagramPacketCls, packetSenderPortFieldId, "senderPort", "I", done); + NETTY_JNI_UTIL_GET_FIELD(env, nativeDatagramPacketCls, packetRecipientAddrFieldId, "recipientAddr", "[B", done); + NETTY_JNI_UTIL_GET_FIELD(env, nativeDatagramPacketCls, packetRecipientAddrLenFieldId, "recipientAddrLen", "I", done); + NETTY_JNI_UTIL_GET_FIELD(env, nativeDatagramPacketCls, packetRecipientScopeIdFieldId, "recipientScopeId", "I", done); + NETTY_JNI_UTIL_GET_FIELD(env, nativeDatagramPacketCls, packetRecipientPortFieldId, "recipientPort", "I", done); + NETTY_JNI_UTIL_GET_FIELD(env, nativeDatagramPacketCls, packetSegmentSizeFieldId, "segmentSize", "I", done); + NETTY_JNI_UTIL_GET_FIELD(env, nativeDatagramPacketCls, packetMemoryAddressFieldId, "memoryAddress", "J", done); + NETTY_JNI_UTIL_GET_FIELD(env, nativeDatagramPacketCls, packetCountFieldId, "count", "I", done); + + ret = NETTY_JNI_UTIL_JNI_VERSION; + + staticPackagePrefix = packagePrefix; + + // Check if there is an epoll_pwait2 system call and also if it works. One some systems it might be there + // but actually is not implemented and so fail with ENOSYS. + // See https://github.com/netty/netty/issues/12343 + if (epoll_pwait2) { + int efd = epoll_create(1); + if (efd != -1) { + struct timespec ts = { 0, 0 }; + struct epoll_event ev; + do { + if (epoll_pwait2(efd, &ev, 1, &ts, NULL) != -1) { + epoll_pwait2_supported = 1; + break; + } + } while(errno == EINTR); + close(efd); + } + } +done: + + netty_jni_util_free_dynamic_methods_table(dynamicMethods, fixed_method_table_size, dynamicMethodsTableSize()); + free(nettyClassName); + + if (ret == JNI_ERR) { + if (staticallyRegistered == 1) { + netty_jni_util_unregister_natives(env, packagePrefix, STATICALLY_CLASSNAME); + } + if (nativeRegistered == 1) { + netty_jni_util_unregister_natives(env, packagePrefix, NATIVE_CLASSNAME); + } + if (linuxsocketOnLoadCalled == 1) { + netty_epoll_linuxsocket_JNI_OnUnLoad(env, packagePrefix); + } + packetSenderAddrFieldId = NULL; + packetSenderAddrLenFieldId = NULL; + packetSenderScopeIdFieldId = NULL; + packetSenderPortFieldId = NULL; + packetRecipientAddrFieldId = NULL; + packetRecipientAddrLenFieldId = NULL; + packetRecipientScopeIdFieldId = NULL; + packetRecipientPortFieldId = NULL; + packetSegmentSizeFieldId = NULL; + packetMemoryAddressFieldId = NULL; + packetCountFieldId = NULL; + } + return ret; +} + +static void netty_epoll_native_JNI_OnUnload(JNIEnv* env) { + netty_epoll_linuxsocket_JNI_OnUnLoad(env, staticPackagePrefix); + + if (register_unix_called == 1) { + register_unix_called = 0; + netty_unix_unregister(env, staticPackagePrefix); + } + + netty_jni_util_unregister_natives(env, staticPackagePrefix, STATICALLY_CLASSNAME); + netty_jni_util_unregister_natives(env, staticPackagePrefix, NATIVE_CLASSNAME); + + if (staticPackagePrefix != NULL) { + free((void *) staticPackagePrefix); + staticPackagePrefix = NULL; + } + + packetSenderAddrFieldId = NULL; + packetSenderAddrLenFieldId = NULL; + packetSenderScopeIdFieldId = NULL; + packetSenderPortFieldId = NULL; + packetRecipientAddrFieldId = NULL; + packetRecipientAddrLenFieldId = NULL; + packetRecipientScopeIdFieldId = NULL; + packetRecipientPortFieldId = NULL; + packetSegmentSizeFieldId = NULL; + packetMemoryAddressFieldId = NULL; + packetCountFieldId = NULL; +} + +// Invoked by the JVM when statically linked + +// We build with -fvisibility=hidden so ensure we mark everything that needs to be visible with JNIEXPORT +// https://mail.openjdk.java.net/pipermail/core-libs-dev/2013-February/014549.html + +// Invoked by the JVM when statically linked +JNIEXPORT jint JNI_OnLoad_netty_transport_native_epoll(JavaVM* vm, void* reserved) { + return netty_jni_util_JNI_OnLoad(vm, reserved, "netty_transport_native_epoll", netty_epoll_native_JNI_OnLoad); +} + +// Invoked by the JVM when statically linked +JNIEXPORT void JNI_OnUnload_netty_transport_native_epoll(JavaVM* vm, void* reserved) { + netty_jni_util_JNI_OnUnload(vm, reserved, netty_epoll_native_JNI_OnUnload); +} + +#ifndef NETTY_BUILD_STATIC +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + return netty_jni_util_JNI_OnLoad(vm, reserved, "netty_transport_native_epoll", netty_epoll_native_JNI_OnLoad); +} + +JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) { + netty_jni_util_JNI_OnUnload(vm, reserved, netty_epoll_native_JNI_OnUnload); +} +#endif /* NETTY_BUILD_STATIC */ diff --git a/netty-channel-epoll-native/src/main/c/netty_epoll_vmsocket.h b/netty-channel-epoll-native/src/main/c/netty_epoll_vmsocket.h new file mode 100644 index 0000000..f1fdbb2 --- /dev/null +++ b/netty-channel-epoll-native/src/main/c/netty_epoll_vmsocket.h @@ -0,0 +1,38 @@ +/* + * Copyright 2023 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#ifndef NETTY_EPOLL_VMSOCKET_H_ +#define NETTY_EPOLL_VMSOCKET_H_ + +#include + +#ifndef AF_VSOCK +#define AF_VSOCK 40 +#endif + +struct sockaddr_vm { + sa_family_t svm_family; + unsigned short svm_reserved1; + unsigned int svm_port; + unsigned int svm_cid; + unsigned char svm_zero[sizeof(struct sockaddr) - + sizeof(sa_family_t) - + sizeof(unsigned short) - + sizeof(unsigned int) - + sizeof(unsigned int)]; +}; + +#endif /* NETTY_EPOLL_VMSOCKET_H_ */ \ No newline at end of file diff --git a/netty-channel-epoll/build.gradle b/netty-channel-epoll/build.gradle index 1f0cb7a..1439d22 100644 --- a/netty-channel-epoll/build.gradle +++ b/netty-channel-epoll/build.gradle @@ -1,4 +1,12 @@ +apply plugin: 'com.google.osdetector' + dependencies { api project(':netty-channel') api project(':netty-channel-unix') + testImplementation project(':netty-testsuite') + testImplementation project(':netty-handler') + testImplementation testLibs.assertj + testImplementation testLibs.rerunner.jupiter + testRuntimeOnly project(path: ':netty-channel-epoll-native', configuration: osdetector.classifier) + testRuntimeOnly project(path: ':netty-tcnative-boringssl-static-native', configuration: osdetector.classifier) } diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/DetectPeerCloseWithoutReadTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/DetectPeerCloseWithoutReadTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/DetectPeerCloseWithoutReadTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/DetectPeerCloseWithoutReadTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollChannelConfigTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollChannelConfigTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollChannelConfigTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollChannelConfigTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollCompositeBufferGatheringWriteTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollCompositeBufferGatheringWriteTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollCompositeBufferGatheringWriteTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollCompositeBufferGatheringWriteTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramChannelConfigTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramChannelConfigTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramChannelConfigTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramChannelConfigTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramChannelTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramChannelTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramChannelTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramChannelTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramConnectNotExistsTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramConnectNotExistsTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramConnectNotExistsTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramConnectNotExistsTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIPv6Test.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIPv6Test.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIPv6Test.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIPv6Test.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIpv6WithIpv4AddrTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIpv6WithIpv4AddrTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIpv6WithIpv4AddrTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIpv6WithIpv4AddrTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramScatteringReadTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramScatteringReadTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramScatteringReadTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramScatteringReadTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6MappedTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6MappedTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6MappedTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6MappedTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6Test.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6Test.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6Test.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6Test.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDetectPeerCloseWithoutReadTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDetectPeerCloseWithoutReadTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDetectPeerCloseWithoutReadTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDetectPeerCloseWithoutReadTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramChannelTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainDatagramChannelTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramChannelTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainDatagramChannelTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramPathTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainDatagramPathTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramPathTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainDatagramPathTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramUnicastTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainDatagramUnicastTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramUnicastTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainDatagramUnicastTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketAddressesTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketAddressesTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketAddressesTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketAddressesTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketDataReadInitialStateTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketDataReadInitialStateTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketDataReadInitialStateTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketDataReadInitialStateTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketEchoTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketEchoTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketEchoTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketEchoTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFdTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketFdTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFdTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketFdTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFileRegionTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketFileRegionTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFileRegionTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketFileRegionTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFixedLengthEchoTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketFixedLengthEchoTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFixedLengthEchoTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketFixedLengthEchoTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketGatheringWriteTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketGatheringWriteTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketGatheringWriteTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketGatheringWriteTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketObjectEchoTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketObjectEchoTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketObjectEchoTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketObjectEchoTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketReuseFdTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketReuseFdTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketReuseFdTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketReuseFdTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketShutdownOutputByPeerTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketShutdownOutputByPeerTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketShutdownOutputByPeerTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketShutdownOutputByPeerTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslClientRenegotiateTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslClientRenegotiateTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslClientRenegotiateTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslClientRenegotiateTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslEchoTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslEchoTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslEchoTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslEchoTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslGreetingTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslGreetingTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslGreetingTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslGreetingTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketStartTlsTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketStartTlsTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketStartTlsTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketStartTlsTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketStringEchoTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketStringEchoTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketStringEchoTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollDomainSocketStringEchoTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketAutoReadTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketAutoReadTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketAutoReadTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketAutoReadTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketConditionalWritabilityTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketConditionalWritabilityTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketConditionalWritabilityTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketConditionalWritabilityTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketDataReadInitialStateTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketDataReadInitialStateTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketDataReadInitialStateTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketDataReadInitialStateTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketExceptionHandlingTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketExceptionHandlingTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketExceptionHandlingTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketExceptionHandlingTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketHalfClosedTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketHalfClosedTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketHalfClosedTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketHalfClosedTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketReadPendingTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketReadPendingTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketReadPendingTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketReadPendingTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketStringEchoBusyWaitTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketStringEchoBusyWaitTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketStringEchoBusyWaitTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollETSocketStringEchoBusyWaitTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollEventLoopTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollEventLoopTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollEventLoopTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollEventLoopTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollJdkLoopbackSocketSslEchoTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollJdkLoopbackSocketSslEchoTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollJdkLoopbackSocketSslEchoTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollJdkLoopbackSocketSslEchoTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollKQueueIovArrayTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollKQueueIovArrayTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollKQueueIovArrayTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollKQueueIovArrayTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketAutoReadTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketAutoReadTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketAutoReadTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketAutoReadTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketConditionalWritabilityTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketConditionalWritabilityTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketConditionalWritabilityTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketConditionalWritabilityTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketDataReadInitialStateTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketDataReadInitialStateTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketDataReadInitialStateTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketDataReadInitialStateTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketExceptionHandlingTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketExceptionHandlingTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketExceptionHandlingTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketExceptionHandlingTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketHalfClosedTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketHalfClosedTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketHalfClosedTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketHalfClosedTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketReadPendingTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketReadPendingTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketReadPendingTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketReadPendingTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketStringEchoBusyWaitTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketStringEchoBusyWaitTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketStringEchoBusyWaitTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollLTSocketStringEchoBusyWaitTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollReuseAddrTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollReuseAddrTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollReuseAddrTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollReuseAddrTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollServerSocketChannelConfigTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollServerSocketChannelConfigTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollServerSocketChannelConfigTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollServerSocketChannelConfigTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketAddressesTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketAddressesTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketAddressesTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketAddressesTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelNotYetConnectedTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelNotYetConnectedTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelNotYetConnectedTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelNotYetConnectedTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketCloseForciblyTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketCloseForciblyTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketCloseForciblyTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketCloseForciblyTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketConnectTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketConnectTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketConnectTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketConnectTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketConnectionAttemptTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketConnectionAttemptTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketConnectionAttemptTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketConnectionAttemptTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketEchoTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketEchoTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketEchoTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketEchoTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketFileRegionTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketFileRegionTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketFileRegionTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketFileRegionTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketFixedLengthEchoTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketFixedLengthEchoTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketFixedLengthEchoTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketFixedLengthEchoTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketGatheringWriteTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketGatheringWriteTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketGatheringWriteTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketGatheringWriteTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketMultipleConnectTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketMultipleConnectTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketMultipleConnectTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketMultipleConnectTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketObjectEchoTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketObjectEchoTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketObjectEchoTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketObjectEchoTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketRstTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketRstTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketRstTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketRstTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputByPeerTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputByPeerTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputByPeerTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputByPeerTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputBySelfTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputBySelfTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputBySelfTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputBySelfTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslClientRenegotiateTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketSslClientRenegotiateTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslClientRenegotiateTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketSslClientRenegotiateTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslEchoTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketSslEchoTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslEchoTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketSslEchoTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslGreetingTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketSslGreetingTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslGreetingTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketSslGreetingTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslSessionReuseTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketSslSessionReuseTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslSessionReuseTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketSslSessionReuseTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStartTlsTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketStartTlsTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStartTlsTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketStartTlsTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoBusyWaitTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoBusyWaitTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoBusyWaitTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoBusyWaitTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTcpMd5Test.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTcpMd5Test.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTcpMd5Test.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTcpMd5Test.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollWriteBeforeRegisteredTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollWriteBeforeRegisteredTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollWriteBeforeRegisteredTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/EpollWriteBeforeRegisteredTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/IovArrayTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/IovArrayTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/IovArrayTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/IovArrayTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/LinuxSocketTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/LinuxSocketTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/LinuxSocketTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/LinuxSocketTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/NativeTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/NativeTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/NativeTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/NativeTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/SocketTest.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/SocketTest.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/SocketTest.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/SocketTest.java diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/UnixTestUtils.java b/netty-channel-epoll/src/test/java/io/netty/channel/epoll/UnixTestUtils.java similarity index 100% rename from netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/UnixTestUtils.java rename to netty-channel-epoll/src/test/java/io/netty/channel/epoll/UnixTestUtils.java diff --git a/netty-channel-epoll-native/src/test/resources/logging.properties b/netty-channel-epoll/src/test/resources/logging.properties similarity index 100% rename from netty-channel-epoll-native/src/test/resources/logging.properties rename to netty-channel-epoll/src/test/resources/logging.properties diff --git a/netty-channel-unix-native/CNakeFile.txt b/netty-channel-unix-native/CNakeFile.txt new file mode 100644 index 0000000..c70d8c6 --- /dev/null +++ b/netty-channel-unix-native/CNakeFile.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) + +project(netty-channel-unix-native VERSION 4.1.104 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +include(GNUInstallDirs) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) + +# jvm +find_package(Java REQUIRED) +# https://stackoverflow.com/questions/51047978/cmake-could-not-find-jni +set(JAVA_AWT_LIBRARY NotNeeded) +set(JAVA_JVM_LIBRARY NotNeeded) +find_package(JNI REQUIRED) + +include_directories(${JNI_INCLUDE_DIRS}) + +# force off-tree build +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) + message(FATAL_ERROR "CMake generation is not allowed within the source directory! + Remove the CMakeCache.txt file and try again from another folder, e.g.: + mkdir build && cd build + cmake .. + ") +endif() + +# default to Release build +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif() + +include_directories(src/main/include) +add_subdirectory(src/main/c) \ No newline at end of file diff --git a/netty-channel-unix-native/build.gradle b/netty-channel-unix-native/build.gradle new file mode 100644 index 0000000..d0cf5d5 --- /dev/null +++ b/netty-channel-unix-native/build.gradle @@ -0,0 +1,10 @@ + +tasks.register("compile", org.xbib.gradle.plugin.c.tasks.ExtendedCCompile) { + group = "build" + source(layout.projectDirectory.files("src/main/c").asFileTree) + includes(layout.projectDirectory.files("src/main/headers")) + toolChain.set(c.localTool("12.3.1", "/usr/bin/gcc", ".o")) + compilerArgs.addAll('-v', '-O3', '-Werror', '-Wno-attributes', '-fPIC', '-fno-omit-frame-pointer', '-Wunused-variable', '-fvisibility=hidden') + targetPlatform.set(c.platform()) + objectFileDir.set(layout.buildDirectory.dir("libs")) +} diff --git a/netty-channel-unix-native/src/main/c/netty_jni_util.c b/netty-channel-unix-native/src/main/c/netty_jni_util.c new file mode 100644 index 0000000..eb2de90 --- /dev/null +++ b/netty-channel-unix-native/src/main/c/netty_jni_util.c @@ -0,0 +1,485 @@ +/* + * Copyright 2020 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _WIN32 +// It's important to have #define _GNU_SOURCE before any other include as otherwise it will not work. +// See http://stackoverflow.com/questions/7296963/gnu-source-and-use-gnu +#define _GNU_SOURCE +#include +#else +#define MAX_DLL_PATH_LEN 2048 +#include +#endif // _WIN32 + +#include +#include +#include "netty_jni_util.h" + +void netty_jni_util_free_dynamic_methods_table(JNINativeMethod* dynamicMethods, jint fixedMethodTableSize, jint fullMethodTableSize) { + if (dynamicMethods != NULL) { + jint i = fixedMethodTableSize; + for (; i < fullMethodTableSize; ++i) { + free(dynamicMethods[i].signature); + } + free(dynamicMethods); + } +} + +void netty_jni_util_free_dynamic_name(char** dynamicName) { + if (dynamicName != NULL && *dynamicName != NULL) { + free(*dynamicName); + *dynamicName = NULL; + } +} + +char* netty_jni_util_prepend(const char* prefix, const char* str) { + if (str == NULL) { + // If str is NULL we should just return NULL as passing NULL to strlen is undefined behavior. + return NULL; + } + if (prefix == NULL) { + char* result = (char*) malloc(sizeof(char) * (strlen(str) + 1)); + if (result == NULL) { + return NULL; + } + strcpy(result, str); + return result; + } + char* result = (char*) malloc(sizeof(char) * (strlen(prefix) + strlen(str) + 1)); + if (result == NULL) { + return NULL; + } + strcpy(result, prefix); + strcat(result, str); + return result; +} + +jint netty_jni_util_register_natives(JNIEnv* env, const char* packagePrefix, const char* className, const JNINativeMethod* methods, jint numMethods) { + char* nettyClassName = NULL; + int retValue = JNI_ERR; + + NETTY_JNI_UTIL_PREPEND(packagePrefix, className, nettyClassName, done); + + jclass nativeCls = (*env)->FindClass(env, nettyClassName); + if (nativeCls != NULL) { + retValue = (*env)->RegisterNatives(env, nativeCls, methods, numMethods); + } +done: + free(nettyClassName); + return retValue; +} + +jint netty_jni_util_unregister_natives(JNIEnv* env, const char* packagePrefix, const char* className) { + char* nettyClassName = NULL; + int retValue = JNI_ERR; + + NETTY_JNI_UTIL_PREPEND(packagePrefix, className, nettyClassName, done); + + jclass nativeCls = (*env)->FindClass(env, nettyClassName); + if (nativeCls != NULL) { + retValue = (*env)->UnregisterNatives(env, nativeCls); + } +done: + free(nettyClassName); + return retValue; +} + +#ifndef NETTY_JNI_UTIL_BUILD_STATIC + +char* netty_jni_util_rstrstr(char* s1rbegin, const char* s1rend, const char* s2) { + if (s1rbegin == NULL || s1rend == NULL || s2 == NULL) { + // Return NULL if any of the parameters is NULL to not risk a segfault + return NULL; + } + size_t s2len = strlen(s2); + char *s = s1rbegin - s2len; + + for (; s >= s1rend; --s) { + if (strncmp(s, s2, s2len) == 0) { + return s; + } + } + return NULL; +} + +#ifdef _WIN32 +static char* netty_jni_util_rstrchar(char* s1rbegin, const char* s1rend, const char c2) { + if (s1rbegin == NULL || s1rend == NULL || c2 == NULL) { + // Return NULL if any of the parameters is NULL to not risk a segfault + return NULL; + } + for (; s1rbegin >= s1rend; --s1rbegin) { + if (*s1rbegin == c2) { + return s1rbegin; + } + } + return NULL; +} +#endif // _WIN32 + +static char* netty_jni_util_strstr_last(const char* haystack, const char* needle) { + if (haystack == NULL || needle == NULL) { + // calling strstr with NULL is undefined behavior. Better just return NULL and not risk a crash. + return NULL; + } + + char* prevptr = NULL; + char* ptr = (char*) haystack; + + while ((ptr = strstr(ptr, needle)) != NULL) { + // Just store the ptr and continue searching. + prevptr = ptr; + ++ptr; + } + return prevptr; +} + +/** + * The expected format of the library name is "lib<>libname" on non windows platforms and "<>libname" on windows, + * where the <> portion is what we will return. + */ +static char* parsePackagePrefix(const char* libraryPathName, const char* libname, jint* status) { + char* packageNameEnd = netty_jni_util_strstr_last(libraryPathName, libname); + if (packageNameEnd == NULL) { + *status = JNI_ERR; + return NULL; + } +#ifdef _WIN32 + // on windows there is no lib prefix so we instead look for the previous path separator or the beginning of the string. + char* packagePrefix = netty_jni_util_rstrchar(packageNameEnd, libraryPathName, '\\'); + if (packagePrefix == NULL) { + // The string does not have to specify a path [1]. + // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/ms683200(v=vs.85).aspx + packagePrefix = libraryPathName; + } else { + packagePrefix += 1; + } +#else + char* packagePrefix = netty_jni_util_rstrstr(packageNameEnd, libraryPathName, "lib"); + if (packagePrefix == NULL) { + *status = JNI_ERR; + return NULL; + } + packagePrefix += 3; +#endif // _WIN32 + + if (packagePrefix == packageNameEnd) { + return NULL; + } + + // packagePrefix length is > 0 + // Make a copy so we can modify the value without impacting libraryPathName. + size_t packagePrefixLen = packageNameEnd - packagePrefix; + char* newPackagePrefix = malloc(packagePrefixLen + 2); // +1 for trailing slash and +1 for \0 + if (newPackagePrefix == NULL) { + *status = JNI_ERR; + return NULL; + } + + // Unmangle the package name, by translating: + // - `_1` to `_` + // - `_` to `/` + // + // Note that we don't unmangle `_0xxxx` because it's extremely unlikely to have a non-ASCII character + // in a package name. For more information, see: + // - JNI specification: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#resolving_native_method_names + // - `NativeLibraryLoader.load()` that mangles a package name. + size_t i; + size_t j = 0; + for (i = 0; i < packagePrefixLen; ++i) { + char ch = packagePrefix[i]; + if (ch != '_') { + newPackagePrefix[j++] = ch; + continue; + } + + char nextCh = packagePrefix[i + 1]; + if (nextCh < '0' || nextCh > '9') { + // No digit after `_`; translate to `/`. + newPackagePrefix[j++] = '/'; + continue; + } + + if (nextCh == '1') { + i++; + newPackagePrefix[j++] = '_'; + } else { + // We don't support _0, _2 .. _9. + fprintf(stderr, + "FATAL: Unsupported escape pattern '_%c' in library name '%s'\n", + nextCh, packagePrefix); + fflush(stderr); + free(newPackagePrefix); + *status = JNI_ERR; + return NULL; + } + } + + // Make sure the prefix ends with `/`. + if (newPackagePrefix[j - 1] != '/') { + newPackagePrefix[j++] = '/'; + } + + // Terminate with `\0`. + newPackagePrefix[j++] = '\0'; + return newPackagePrefix; +} + +#endif /* NETTY_JNI_UTIL_BUILD_STATIC */ + +/* Fix missing Dl_info & dladdr in AIX + * The code is taken from netbsd.org (src/crypto/external/bsd/openssl/dist/crypto/dso/dso_dlfcn.c) + * except strlcpy & strlcat (those are taken from openbsd.org (src/lib/libc/string)) + */ +#ifdef _AIX +/*- + * See IBM's AIX Version 7.2, Technical Reference: + * Base Operating System and Extensions, Volume 1 and 2 + * https://www.ibm.com/support/knowledgecenter/ssw_aix_72/com.ibm.aix.base/technicalreferences.htm + */ +#include +#include + +/* strlcpy: + * Copy string src to buffer dst of size dsize. At most dsize-1 + * chars will be copied. Always NUL terminates (unless dsize == 0). + * Returns strlen(src); if retval >= dsize, truncation occurred. + */ +size_t strlcpy(char *dst, const char *src, size_t dsize) +{ + const char *osrc = src; + size_t nleft = dsize; + + /* Copy as many bytes as will fit. */ + if (nleft != 0) { + while (--nleft != 0) { + if ((*dst++ = *src++) == '\0') { + break; + } + } + } + + /* Not enough room in dst, add NUL and traverse rest of src. */ + if (nleft == 0) { + if (dsize != 0) { + *dst = '\0'; /* NUL-terminate dst */ + } + while (*src++) { + ; + } + } + + return src - osrc - 1; /* count does not include NUL */ +} + +/* strlcat: + * Appends src to string dst of size dsize (unlike strncat, dsize is the + * full size of dst, not space left). At most dsize-1 characters + * will be copied. Always NUL terminates (unless dsize <= strlen(dst)). + * Returns strlen(src) + MIN(dsize, strlen(initial dst)). + * If retval >= dsize, truncation occurred. + */ +size_t strlcat(char *dst, const char *src, size_t dsize) +{ + const char *odst = dst; + const char *osrc = src; + size_t n = dsize; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end. */ + while (n-- != 0 && *dst != '\0') { + dst++; + } + dlen = dst - odst; + n = dsize - dlen; + + if (n-- == 0) { + return dlen + strlen(src); + } + while (*src != '\0') { + if (n != 0) { + *dst++ = *src; + n--; + } + src++; + } + *dst = '\0'; + + return dlen + src - osrc; /* count does not include NUL */ +} + +/* ~ 64 * (sizeof(struct ld_info) + _XOPEN_PATH_MAX + _XOPEN_NAME_MAX) */ +# define DLFCN_LDINFO_SIZE 86976 +typedef struct Dl_info { + const char *dli_fname; +} Dl_info; +/* + * This dladdr()-implementation will also find the ptrgl (Pointer Glue) virtual + * address of a function, which is just located in the DATA segment instead of + * the TEXT segment. + */ +static int dladdr(void *ptr, Dl_info *dl) +{ + uintptr_t addr = (uintptr_t)ptr; + struct ld_info *ldinfos; + struct ld_info *next_ldi; + struct ld_info *this_ldi; + + if ((ldinfos = malloc(DLFCN_LDINFO_SIZE)) == NULL) { + dl->dli_fname = NULL; + return 0; + } + + if ((loadquery(L_GETINFO, (void *)ldinfos, DLFCN_LDINFO_SIZE)) < 0) { + /*- + * Error handling is done through errno and dlerror() reading errno: + * ENOMEM (ldinfos buffer is too small), + * EINVAL (invalid flags), + * EFAULT (invalid ldinfos ptr) + */ + free((void *)ldinfos); + dl->dli_fname = NULL; + return 0; + } + next_ldi = ldinfos; + + do { + this_ldi = next_ldi; + if (((addr >= (uintptr_t)this_ldi->ldinfo_textorg) + && (addr < ((uintptr_t)this_ldi->ldinfo_textorg + + this_ldi->ldinfo_textsize))) + || ((addr >= (uintptr_t)this_ldi->ldinfo_dataorg) + && (addr < ((uintptr_t)this_ldi->ldinfo_dataorg + + this_ldi->ldinfo_datasize)))) { + char *buffer = NULL; + char *member = NULL; + size_t buffer_sz; + size_t member_len; + + buffer_sz = strlen(this_ldi->ldinfo_filename) + 1; + member = this_ldi->ldinfo_filename + buffer_sz; + if ((member_len = strlen(member)) > 0) { + buffer_sz += 1 + member_len + 1; + } + if ((buffer = malloc(buffer_sz)) != NULL) { + strlcpy(buffer, this_ldi->ldinfo_filename, buffer_sz); + if (member_len > 0) { + /* + * Need to respect a possible member name and not just + * returning the path name in this case. See docs: + * sys/ldr.h, loadquery() and dlopen()/RTLD_MEMBER. + */ + strlcat(buffer, "(", buffer_sz); + strlcat(buffer, member, buffer_sz); + strlcat(buffer, ")", buffer_sz); + } + dl->dli_fname = buffer; + } + break; + } else { + next_ldi = (struct ld_info *)((uintptr_t)this_ldi + + this_ldi->ldinfo_next); + } + } while (this_ldi->ldinfo_next); + free((void *)ldinfos); + return dl->dli_fname != NULL; +} +#endif /* _AIX */ + +jint netty_jni_util_JNI_OnLoad(JavaVM* vm, void* reserved, const char* libname, jint (*load_function)(JNIEnv*, const char*)) { + JNIEnv* env = NULL; + if ((*vm)->GetEnv(vm, (void**) &env, NETTY_JNI_UTIL_JNI_VERSION) != JNI_OK) { + fprintf(stderr, "FATAL: JNI version mismatch"); + fflush(stderr); + return JNI_ERR; + } + +#ifndef NETTY_JNI_UTIL_BUILD_STATIC + jint status = 0; + const char* name = NULL; +#ifndef _WIN32 + Dl_info dlinfo; + // We need to use an address of a function that is uniquely part of this library, so choose a static + // function. See https://github.com/netty/netty/issues/4840. + if (!dladdr((void*) parsePackagePrefix, &dlinfo)) { + fprintf(stderr, "FATAL: %s JNI call to dladdr failed!\n", libname); + fflush(stderr); + return JNI_ERR; + } + name = dlinfo.dli_fname; +#else + HMODULE module = NULL; + if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (void*) parsePackagePrefix, &module) == 0){ + fprintf(stderr, "FATAL: %s JNI call to GetModuleHandleExA failed!\n", libname); + fflush(stderr); + return JNI_ERR; + } + + // add space for \0 termination as this is not automatically included for windows XP + // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683197(v=vs.85).aspx + char dllPath[MAX_DLL_PATH_LEN + 1]; + int dllPathLen = GetModuleFileNameA(module, dllPath, MAX_DLL_PATH_LEN); + if (dllPathLen == 0) { + fprintf(stderr, "FATAL: %s JNI call to GetModuleFileNameA failed!\n", libname); + fflush(stderr); + return JNI_ERR; + } else { + // ensure we null terminate as this is not automatically done on windows xp + dllPath[dllPathLen] = '\0'; + } + + name = dllPath; +#endif + char* packagePrefix = parsePackagePrefix(name, libname, &status); + + if (status == JNI_ERR) { + fprintf(stderr, "FATAL: %s encountered unexpected library path: %s\n", name, libname); + fflush(stderr); + return JNI_ERR; + } +#else + char* packagePrefix = NULL; +#endif /* NETTY_JNI_UTIL_BUILD_STATIC */ + + jint ret = load_function(env, packagePrefix); + return ret; +} + +void netty_jni_util_JNI_OnUnload(JavaVM* vm, void* reserved, void (*unload_function)(JNIEnv*)) { + JNIEnv* env = NULL; + if ((*vm)->GetEnv(vm, (void**) &env, NETTY_JNI_UTIL_JNI_VERSION) != JNI_OK) { + fprintf(stderr, "FATAL: JNI version mismatch"); + fflush(stderr); + // Something is wrong but nothing we can do about this :( + return; + } + unload_function(env); +} diff --git a/netty-channel-unix-native/src/main/c/netty_unix.c b/netty-channel-unix-native/src/main/c/netty_unix.c new file mode 100644 index 0000000..7b3cbee --- /dev/null +++ b/netty-channel-unix-native/src/main/c/netty_unix.c @@ -0,0 +1,87 @@ +/* + * Copyright 2020 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#include "netty_unix_jni.h" +#include "netty_unix.h" +#include "netty_unix_buffer.h" +#include "netty_unix_errors.h" +#include "netty_unix_filedescriptor.h" +#include "netty_unix_limits.h" +#include "netty_unix_socket.h" +#include "netty_unix_util.h" + +// IMPORTANT: If you add any NETTY_JNI_UTIL_LOAD_CLASS or NETTY_JNI_UTIL_FIND_CLASS calls you also need to update +// Unix to reflect that. +jint netty_unix_register(JNIEnv* env, const char* packagePrefix) { + int limitsOnLoadCalled = 0; + int errorsOnLoadCalled = 0; + int filedescriptorOnLoadCalled = 0; + int socketOnLoadCalled = 0; + int bufferOnLoadCalled = 0; + + // Load all c modules that we depend upon + if (netty_unix_limits_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + goto error; + } + limitsOnLoadCalled = 1; + + if (netty_unix_errors_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + goto error; + } + errorsOnLoadCalled = 1; + + if (netty_unix_filedescriptor_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + goto error; + } + filedescriptorOnLoadCalled = 1; + + if (netty_unix_socket_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + goto error; + } + socketOnLoadCalled = 1; + + if (netty_unix_buffer_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + goto error; + } + bufferOnLoadCalled = 1; + + return NETTY_JNI_UTIL_JNI_VERSION; +error: + if (limitsOnLoadCalled == 1) { + netty_unix_limits_JNI_OnUnLoad(env, packagePrefix); + } + if (errorsOnLoadCalled == 1) { + netty_unix_errors_JNI_OnUnLoad(env, packagePrefix); + } + if (filedescriptorOnLoadCalled == 1) { + netty_unix_filedescriptor_JNI_OnUnLoad(env, packagePrefix); + } + if (socketOnLoadCalled == 1) { + netty_unix_socket_JNI_OnUnLoad(env, packagePrefix); + } + if (bufferOnLoadCalled == 1) { + netty_unix_buffer_JNI_OnUnLoad(env, packagePrefix); + } + return JNI_ERR; +} + +void netty_unix_unregister(JNIEnv* env, const char* packagePrefix) { + netty_unix_limits_JNI_OnUnLoad(env, packagePrefix); + netty_unix_errors_JNI_OnUnLoad(env, packagePrefix); + netty_unix_filedescriptor_JNI_OnUnLoad(env, packagePrefix); + netty_unix_socket_JNI_OnUnLoad(env, packagePrefix); + netty_unix_buffer_JNI_OnUnLoad(env, packagePrefix); +} + diff --git a/netty-channel-unix-native/src/main/c/netty_unix_buffer.c b/netty-channel-unix-native/src/main/c/netty_unix_buffer.c new file mode 100644 index 0000000..9f957eb --- /dev/null +++ b/netty-channel-unix-native/src/main/c/netty_unix_buffer.c @@ -0,0 +1,59 @@ +/* + * Copyright 2018 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#include "netty_unix_jni.h" +#include "netty_unix_util.h" +#include "netty_unix_buffer.h" +#include "netty_jni_util.h" + +#define BUFFER_CLASSNAME "io/netty/channel/unix/Buffer" + +// JNI Registered Methods Begin +static jlong netty_unix_buffer_memoryAddress0(JNIEnv* env, jclass clazz, jobject buffer) { + return (jlong) (*env)->GetDirectBufferAddress(env, buffer); +} + +static jint netty_unix_buffer_addressSize0(JNIEnv* env, jclass clazz) { + return (jint) sizeof(int*); +} + +// JNI Registered Methods End + +// JNI Method Registration Table Begin +static const JNINativeMethod statically_referenced_fixed_method_table[] = { + { "memoryAddress0", "(Ljava/nio/ByteBuffer;)J", (void *) netty_unix_buffer_memoryAddress0 }, + { "addressSize0", "()I", (void *) netty_unix_buffer_addressSize0 } +}; +static const jint statically_referenced_fixed_method_table_size = sizeof(statically_referenced_fixed_method_table) / sizeof(statically_referenced_fixed_method_table[0]); +// JNI Method Registration Table End + +// IMPORTANT: If you add any NETTY_JNI_UTIL_LOAD_CLASS or NETTY_JNI_UTIL_FIND_CLASS calls you also need to update +// Unix to reflect that. +jint netty_unix_buffer_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { + // We must register the statically referenced methods first! + if (netty_jni_util_register_natives(env, + packagePrefix, + BUFFER_CLASSNAME, + statically_referenced_fixed_method_table, + statically_referenced_fixed_method_table_size) != 0) { + return JNI_ERR; + } + + return NETTY_JNI_UTIL_JNI_VERSION; +} + +void netty_unix_buffer_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix) { + netty_jni_util_unregister_natives(env, packagePrefix, BUFFER_CLASSNAME); +} diff --git a/netty-channel-unix-native/src/main/c/netty_unix_errors.c b/netty-channel-unix-native/src/main/c/netty_unix_errors.c new file mode 100644 index 0000000..1dcac70 --- /dev/null +++ b/netty-channel-unix-native/src/main/c/netty_unix_errors.c @@ -0,0 +1,281 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include "netty_unix_errors.h" +#include "netty_unix_jni.h" +#include "netty_unix_util.h" +#include "netty_jni_util.h" + +#define ERRORS_CLASSNAME "io/netty/channel/unix/ErrorsStaticallyReferencedJniMethods" + +static jweak channelExceptionClassWeak = NULL; +static jclass oomErrorClass = NULL; +static jclass runtimeExceptionClass = NULL; +static jclass ioExceptionClass = NULL; +static jclass portUnreachableExceptionClass = NULL; +static jclass closedChannelExceptionClass = NULL; +static jmethodID closedChannelExceptionMethodId = NULL; + +/** + Our `strerror_r` wrapper makes sure that the function is XSI compliant, + even on platforms where the GNU variant is exposed. + Note: `strerrbuf` must be initialized to all zeros prior to calling this function. + XSI or GNU functions do not have such a requirement, but our wrappers do. + */ +#if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 || __APPLE__) && ! _GNU_SOURCE + static inline int strerror_r_xsi(int errnum, char *strerrbuf, size_t buflen) { + return strerror_r(errnum, strerrbuf, buflen); + } +#else + static inline int strerror_r_xsi(int errnum, char *strerrbuf, size_t buflen) { + char* tmp = strerror_r(errnum, strerrbuf, buflen); + if (strerrbuf[0] == '\0') { + // Our output buffer was not used. Copy from tmp. + strncpy(strerrbuf, tmp, buflen - 1); // Use (buflen - 1) to avoid overwriting terminating \0. + } + if (errno != 0) { + return -1; + } + return 0; + } +#endif + +/** Notice: every usage of exceptionMessage needs to release the allocated memory for the sequence of char */ +static char* exceptionMessage(char* msg, int error) { + if (error < 0) { + // Error may be negative because some functions return negative values. We should make sure it is always + // positive when passing to standard library functions. + error = -error; + } + + int buflen = 32; + char* strerrbuf = NULL; + int result = 0; + do { + buflen = buflen * 2; + if (buflen >= 2048) { + break; // Limit buffer growth. + } + if (strerrbuf != NULL) { + free(strerrbuf); + } + strerrbuf = calloc(buflen, sizeof(char)); + result = strerror_r_xsi(error, strerrbuf, buflen); + if (result == -1) { + result = errno; + } + } while (result == ERANGE); + + char* combined = netty_jni_util_prepend(msg, strerrbuf); + free(strerrbuf); + return combined; +} + +// Exported C methods +void netty_unix_errors_throwRuntimeException(JNIEnv* env, char* message) { + (*env)->ThrowNew(env, runtimeExceptionClass, message); +} + +void netty_unix_errors_throwRuntimeExceptionErrorNo(JNIEnv* env, char* message, int errorNumber) { + char* allocatedMessage = exceptionMessage(message, errorNumber); + if (allocatedMessage == NULL) { + return; + } + (*env)->ThrowNew(env, runtimeExceptionClass, allocatedMessage); + free(allocatedMessage); +} + +void netty_unix_errors_throwChannelExceptionErrorNo(JNIEnv* env, char* message, int errorNumber) { + jclass channelExceptionClass = NULL; + char* allocatedMessage = exceptionMessage(message, errorNumber); + if (allocatedMessage == NULL) { + return; + } + + NETTY_JNI_UTIL_NEW_LOCAL_FROM_WEAK(env, channelExceptionClass, channelExceptionClassWeak, done); + + (*env)->ThrowNew(env, channelExceptionClass, allocatedMessage); +done: + + NETTY_JNI_UTIL_DELETE_LOCAL(env, channelExceptionClass); + free(allocatedMessage); +} + +void netty_unix_errors_throwIOException(JNIEnv* env, char* message) { + (*env)->ThrowNew(env, ioExceptionClass, message); +} + +void netty_unix_errors_throwPortUnreachableException(JNIEnv* env, char* message) { + (*env)->ThrowNew(env, portUnreachableExceptionClass, message); +} + +void netty_unix_errors_throwIOExceptionErrorNo(JNIEnv* env, char* message, int errorNumber) { + char* allocatedMessage = exceptionMessage(message, errorNumber); + if (allocatedMessage == NULL) { + return; + } + (*env)->ThrowNew(env, ioExceptionClass, allocatedMessage); + free(allocatedMessage); +} + +void netty_unix_errors_throwClosedChannelException(JNIEnv* env) { + jobject exception = (*env)->NewObject(env, closedChannelExceptionClass, closedChannelExceptionMethodId); + if (exception == NULL) { + return; + } + (*env)->Throw(env, exception); +} + +void netty_unix_errors_throwOutOfMemoryError(JNIEnv* env) { + (*env)->ThrowNew(env, oomErrorClass, ""); +} + +// JNI Registered Methods Begin +static jint netty_unix_errors_errnoENOENT(JNIEnv* env, jclass clazz) { + return ENOENT; +} + +static jint netty_unix_errors_errnoENOTCONN(JNIEnv* env, jclass clazz) { + return ENOTCONN; +} + +static jint netty_unix_errors_errnoEBADF(JNIEnv* env, jclass clazz) { + return EBADF; +} + +static jint netty_unix_errors_errnoEPIPE(JNIEnv* env, jclass clazz) { + return EPIPE; +} + +static jint netty_unix_errors_errnoECONNRESET(JNIEnv* env, jclass clazz) { + return ECONNRESET; +} + +static jint netty_unix_errors_errnoEAGAIN(JNIEnv* env, jclass clazz) { + return EAGAIN; +} + +static jint netty_unix_errors_errnoEWOULDBLOCK(JNIEnv* env, jclass clazz) { + return EWOULDBLOCK; +} + +static jint netty_unix_errors_errnoEINPROGRESS(JNIEnv* env, jclass clazz) { + return EINPROGRESS; +} + +static jint netty_unix_errors_errorECONNREFUSED(JNIEnv* env, jclass clazz) { + return ECONNREFUSED; +} + +static jint netty_unix_errors_errorEISCONN(JNIEnv* env, jclass clazz) { + return EISCONN; +} + +static jint netty_unix_errors_errorEALREADY(JNIEnv* env, jclass clazz) { + return EALREADY; +} + +static jint netty_unix_errors_errorENETUNREACH(JNIEnv* env, jclass clazz) { + return ENETUNREACH; +} + +static jint netty_unix_errors_errorEHOSTUNREACH(JNIEnv* env, jclass clazz) { + return EHOSTUNREACH; +} + +static jstring netty_unix_errors_strError(JNIEnv* env, jclass clazz, jint error) { + return (*env)->NewStringUTF(env, strerror(error)); +} +// JNI Registered Methods End + +// JNI Method Registration Table Begin +static const JNINativeMethod statically_referenced_fixed_method_table[] = { + { "errnoENOENT", "()I", (void *) netty_unix_errors_errnoENOENT }, + { "errnoENOTCONN", "()I", (void *) netty_unix_errors_errnoENOTCONN }, + { "errnoEBADF", "()I", (void *) netty_unix_errors_errnoEBADF }, + { "errnoEPIPE", "()I", (void *) netty_unix_errors_errnoEPIPE }, + { "errnoECONNRESET", "()I", (void *) netty_unix_errors_errnoECONNRESET }, + { "errnoEAGAIN", "()I", (void *) netty_unix_errors_errnoEAGAIN }, + { "errnoEWOULDBLOCK", "()I", (void *) netty_unix_errors_errnoEWOULDBLOCK }, + { "errnoEINPROGRESS", "()I", (void *) netty_unix_errors_errnoEINPROGRESS }, + { "errorECONNREFUSED", "()I", (void *) netty_unix_errors_errorECONNREFUSED }, + { "errorEISCONN", "()I", (void *) netty_unix_errors_errorEISCONN }, + { "errorEALREADY", "()I", (void *) netty_unix_errors_errorEALREADY }, + { "errorENETUNREACH", "()I", (void *) netty_unix_errors_errorENETUNREACH }, + { "errorEHOSTUNREACH", "()I", (void *) netty_unix_errors_errorEHOSTUNREACH }, + { "strError", "(I)Ljava/lang/String;", (void *) netty_unix_errors_strError } +}; +static const jint statically_referenced_fixed_method_table_size = sizeof(statically_referenced_fixed_method_table) / sizeof(statically_referenced_fixed_method_table[0]); +// JNI Method Registration Table End + +// IMPORTANT: If you add any NETTY_JNI_UTIL_LOAD_CLASS or NETTY_JNI_UTIL_FIND_CLASS calls you also need to update +// Unix to reflect that. +jint netty_unix_errors_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { + char* nettyClassName = NULL; + jclass channelExceptionClass = NULL; + // We must register the statically referenced methods first! + if (netty_jni_util_register_natives(env, + packagePrefix, + ERRORS_CLASSNAME, + statically_referenced_fixed_method_table, + statically_referenced_fixed_method_table_size) != 0) { + return JNI_ERR; + } + + NETTY_JNI_UTIL_LOAD_CLASS(env, oomErrorClass, "java/lang/OutOfMemoryError", error); + + NETTY_JNI_UTIL_LOAD_CLASS(env, runtimeExceptionClass, "java/lang/RuntimeException", error); + + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/ChannelException", nettyClassName, error); + + + NETTY_JNI_UTIL_LOAD_CLASS_WEAK(env, channelExceptionClassWeak, nettyClassName, error); + NETTY_JNI_UTIL_NEW_LOCAL_FROM_WEAK(env, channelExceptionClass, channelExceptionClassWeak, error); + + netty_jni_util_free_dynamic_name(&nettyClassName); + + NETTY_JNI_UTIL_LOAD_CLASS(env, closedChannelExceptionClass, "java/nio/channels/ClosedChannelException", error); + NETTY_JNI_UTIL_GET_METHOD(env, closedChannelExceptionClass, closedChannelExceptionMethodId, "", "()V", error); + + NETTY_JNI_UTIL_LOAD_CLASS(env, ioExceptionClass, "java/io/IOException", error); + + NETTY_JNI_UTIL_LOAD_CLASS(env, portUnreachableExceptionClass, "java/net/PortUnreachableException", error); + + + NETTY_JNI_UTIL_DELETE_LOCAL(env, channelExceptionClass); + return NETTY_JNI_UTIL_JNI_VERSION; +error: + free(nettyClassName); + + NETTY_JNI_UTIL_DELETE_LOCAL(env, channelExceptionClass); + + return JNI_ERR; +} + +void netty_unix_errors_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix) { + // delete global references so the GC can collect them + NETTY_JNI_UTIL_UNLOAD_CLASS(env, oomErrorClass); + NETTY_JNI_UTIL_UNLOAD_CLASS(env, runtimeExceptionClass); + NETTY_JNI_UTIL_UNLOAD_CLASS_WEAK(env, channelExceptionClassWeak); + NETTY_JNI_UTIL_UNLOAD_CLASS(env, ioExceptionClass); + NETTY_JNI_UTIL_UNLOAD_CLASS(env, portUnreachableExceptionClass); + NETTY_JNI_UTIL_UNLOAD_CLASS(env, closedChannelExceptionClass); + + netty_jni_util_unregister_natives(env, packagePrefix, ERRORS_CLASSNAME); +} diff --git a/netty-channel-unix-native/src/main/c/netty_unix_filedescriptor.c b/netty-channel-unix-native/src/main/c/netty_unix_filedescriptor.c new file mode 100644 index 0000000..a626e4d --- /dev/null +++ b/netty-channel-unix-native/src/main/c/netty_unix_filedescriptor.c @@ -0,0 +1,323 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "netty_unix_errors.h" +#include "netty_unix_filedescriptor.h" +#include "netty_unix_jni.h" +#include "netty_unix_util.h" +#include "netty_jni_util.h" + +#define FILEDESCRIPTOR_CLASSNAME "io/netty/channel/unix/FileDescriptor" + +static jmethodID posId = NULL; +static jmethodID limitId = NULL; +static jfieldID posFieldId = NULL; +static jfieldID limitFieldId = NULL; + +// Optional external methods +extern int pipe2(int pipefd[2], int flags) __attribute__((weak)) __attribute__((weak_import)); + +static jint _write(JNIEnv* env, jclass clazz, jint fd, void* buffer, jint pos, jint limit) { + ssize_t res; + int err; + do { + res = write(fd, buffer + pos, (size_t) (limit - pos)); + // keep on writing if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + return -err; + } + return (jint) res; +} + +static jlong _writev(JNIEnv* env, jclass clazz, jint fd, struct iovec* iov, jint length) { + ssize_t res; + int err; + do { + res = writev(fd, iov, length); + // keep on writing if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + return -err; + } + return (jlong) res; +} + +static jint _read(JNIEnv* env, jclass clazz, jint fd, void* buffer, jint pos, jint limit) { + ssize_t res; + int err; + do { + res = read(fd, buffer + pos, (size_t) (limit - pos)); + // Keep on reading if we was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + return -err; + } + return (jint) res; +} + +// JNI Registered Methods Begin +static jint netty_unix_filedescriptor_close(JNIEnv* env, jclass clazz, jint fd) { + if (close(fd) < 0) { + // There is really nothing "sane" we can do when EINTR was reported on close. So just ignore it and "assume" + // everything is fine == we closed the file descriptor. + // + // For more details see: + // - https://bugs.chromium.org/p/chromium/issues/detail?id=269623 + // - https://lwn.net/Articles/576478/ + if (errno != EINTR) { + return -errno; + } + } + return 0; +} + +static jint netty_unix_filedescriptor_open(JNIEnv* env, jclass clazz, jstring path) { + const char* f_path = (*env)->GetStringUTFChars(env, path, 0); + + int res = open(f_path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + (*env)->ReleaseStringUTFChars(env, path, f_path); + + if (res < 0) { + return -errno; + } + return res; +} + +static jint netty_unix_filedescriptor_write(JNIEnv* env, jclass clazz, jint fd, jobject jbuffer, jint pos, jint limit) { + // We check that GetDirectBufferAddress will not return NULL in OnLoad + return _write(env, clazz, fd, (*env)->GetDirectBufferAddress(env, jbuffer), pos, limit); +} + +static jint netty_unix_filedescriptor_writeAddress(JNIEnv* env, jclass clazz, jint fd, jlong address, jint pos, jint limit) { + return _write(env, clazz, fd, (void*) (intptr_t) address, pos, limit); +} + + +static jlong netty_unix_filedescriptor_writevAddresses(JNIEnv* env, jclass clazz, jint fd, jlong memoryAddress, jint length) { + struct iovec* iov = (struct iovec*) (intptr_t) memoryAddress; + return _writev(env, clazz, fd, iov, length); +} + +static jlong netty_unix_filedescriptor_writev(JNIEnv* env, jclass clazz, jint fd, jobjectArray buffers, const jint offset, jint length, jlong maxBytesToWrite) { + struct iovec iov[length]; + struct iovec* iovptr = iov; + int i; + int num = offset + length; + if (posFieldId != NULL && limitFieldId != NULL) { + for (i = offset; i < num; ++i) { + jobject bufObj = (*env)->GetObjectArrayElement(env, buffers, i); + jint pos = (*env)->GetIntField(env, bufObj, posFieldId); + jint limit = (*env)->GetIntField(env, bufObj, limitFieldId); + size_t bytesLength = (size_t) (limit - pos); + if (bytesLength > maxBytesToWrite && i != offset) { + length = i - offset; + break; + } + maxBytesToWrite -= bytesLength; + void* buffer = (*env)->GetDirectBufferAddress(env, bufObj); + // We check that GetDirectBufferAddress will not return NULL in OnLoad + iovptr->iov_base = buffer + pos; + iovptr->iov_len = bytesLength; + ++iovptr; + + // Explicit delete local reference as otherwise the local references will only be released once the native method returns. + // Also there may be a lot of these and JNI specification only specify that 16 must be able to be created. + // + // See https://github.com/netty/netty/issues/2623 + (*env)->DeleteLocalRef(env, bufObj); + } + } else if (posFieldId != NULL && limitFieldId == NULL) { + for (i = offset; i < num; ++i) { + jobject bufObj = (*env)->GetObjectArrayElement(env, buffers, i); + jint pos = (*env)->GetIntField(env, bufObj, posFieldId); + jint limit = (*env)->CallIntMethod(env, bufObj, limitId, NULL); + size_t bytesLength = (size_t) (limit - pos); + if (bytesLength > maxBytesToWrite && i != offset) { + length = i - offset; + break; + } + maxBytesToWrite -= bytesLength; + void* buffer = (*env)->GetDirectBufferAddress(env, bufObj); + // We check that GetDirectBufferAddress will not return NULL in OnLoad + iovptr->iov_base = buffer + pos; + iovptr->iov_len = bytesLength; + ++iovptr; + + // Explicit delete local reference as otherwise the local references will only be released once the native method returns. + // Also there may be a lot of these and JNI specification only specify that 16 must be able to be created. + // + // See https://github.com/netty/netty/issues/2623 + (*env)->DeleteLocalRef(env, bufObj); + } + } else if (limitFieldId != NULL) { + for (i = offset; i < num; ++i) { + jobject bufObj = (*env)->GetObjectArrayElement(env, buffers, i); + jint pos = (*env)->CallIntMethod(env, bufObj, posId, NULL); + jint limit = (*env)->GetIntField(env, bufObj, limitFieldId); + size_t bytesLength = (size_t) (limit - pos); + if (bytesLength > maxBytesToWrite && i != offset) { + length = i - offset; + break; + } + maxBytesToWrite -= bytesLength; + void* buffer = (*env)->GetDirectBufferAddress(env, bufObj); + // We check that GetDirectBufferAddress will not return NULL in OnLoad + iovptr->iov_base = buffer + pos; + iovptr->iov_len = bytesLength; + ++iovptr; + + // Explicit delete local reference as otherwise the local references will only be released once the native method returns. + // Also there may be a lot of these and JNI specification only specify that 16 must be able to be created. + // + // See https://github.com/netty/netty/issues/2623 + (*env)->DeleteLocalRef(env, bufObj); + } + } else { + for (i = offset; i < num; ++i) { + jobject bufObj = (*env)->GetObjectArrayElement(env, buffers, i); + jint pos = (*env)->CallIntMethod(env, bufObj, posId, NULL); + jint limit = (*env)->CallIntMethod(env, bufObj, limitId, NULL); + size_t bytesLength = (size_t) (limit - pos); + if (bytesLength > maxBytesToWrite && i != offset) { + length = i - offset; + break; + } + maxBytesToWrite -= bytesLength; + void* buffer = (*env)->GetDirectBufferAddress(env, bufObj); + // We check that GetDirectBufferAddress will not return NULL in OnLoad + iovptr->iov_base = buffer + pos; + iovptr->iov_len = bytesLength; + ++iovptr; + + // Explicit delete local reference as otherwise the local references will only be released once the native method returns. + // Also there may be a lot of these and JNI specification only specify that 16 must be able to be created. + // + // See https://github.com/netty/netty/issues/2623 + (*env)->DeleteLocalRef(env, bufObj); + } + } + return _writev(env, clazz, fd, iov, length); +} + +static jint netty_unix_filedescriptor_read(JNIEnv* env, jclass clazz, jint fd, jobject jbuffer, jint pos, jint limit) { + // We check that GetDirectBufferAddress will not return NULL in OnLoad + return _read(env, clazz, fd, (*env)->GetDirectBufferAddress(env, jbuffer), pos, limit); +} + +static jint netty_unix_filedescriptor_readAddress(JNIEnv* env, jclass clazz, jint fd, jlong address, jint pos, jint limit) { + return _read(env, clazz, fd, (void*) (intptr_t) address, pos, limit); +} + +static jlong netty_unix_filedescriptor_newPipe(JNIEnv* env, jclass clazz) { + int fd[2]; + if (pipe2) { + // we can just use pipe2 and so save extra syscalls; + if (pipe2(fd, O_NONBLOCK) != 0) { + return -errno; + } + } else { + if (pipe(fd) == 0) { + if (fcntl(fd[0], F_SETFD, O_NONBLOCK) < 0) { + int err = errno; + close(fd[0]); + close(fd[1]); + return -err; + } + if (fcntl(fd[1], F_SETFD, O_NONBLOCK) < 0) { + int err = errno; + close(fd[0]); + close(fd[1]); + return -err; + } + } else { + return -errno; + } + } + + // encode the fds into a 64 bit value + return (((jlong) fd[0]) << 32) | fd[1]; +} +// JNI Registered Methods End + +// JNI Method Registration Table Begin +static const JNINativeMethod method_table[] = { + { "close", "(I)I", (void *) netty_unix_filedescriptor_close }, + { "open", "(Ljava/lang/String;)I", (void *) netty_unix_filedescriptor_open }, + { "write", "(ILjava/nio/ByteBuffer;II)I", (void *) netty_unix_filedescriptor_write }, + { "writeAddress", "(IJII)I", (void *) netty_unix_filedescriptor_writeAddress }, + { "writevAddresses", "(IJI)J", (void *) netty_unix_filedescriptor_writevAddresses }, + { "writev", "(I[Ljava/nio/ByteBuffer;IIJ)J", (void *) netty_unix_filedescriptor_writev }, + { "read", "(ILjava/nio/ByteBuffer;II)I", (void *) netty_unix_filedescriptor_read }, + { "readAddress", "(IJII)I", (void *) netty_unix_filedescriptor_readAddress }, + { "newPipe", "()J", (void *) netty_unix_filedescriptor_newPipe } +}; +static const jint method_table_size = sizeof(method_table) / sizeof(method_table[0]); +// JNI Method Registration Table End + +// IMPORTANT: If you add any NETTY_JNI_UTIL_LOAD_CLASS or NETTY_JNI_UTIL_FIND_CLASS calls you also need to update +// Unix to reflect that. +jint netty_unix_filedescriptor_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { + int ret = JNI_ERR; + void* mem = NULL; + if (netty_jni_util_register_natives(env, packagePrefix, FILEDESCRIPTOR_CLASSNAME, method_table, method_table_size) != 0) { + goto done; + } + if ((mem = malloc(1)) == NULL) { + goto done; + } + jobject directBuffer = (*env)->NewDirectByteBuffer(env, mem, 1); + if (directBuffer == NULL) { + goto done; + } + if ((*env)->GetDirectBufferAddress(env, directBuffer) == NULL) { + goto done; + } + jclass cls = (*env)->GetObjectClass(env, directBuffer); + if (cls == NULL) { + goto done; + } + + // Get the method id for Buffer.position() and Buffer.limit(). These are used as fallback if + // it is not possible to obtain the position and limit using the fields directly. + NETTY_JNI_UTIL_GET_METHOD(env, cls, posId, "position", "()I", done); + NETTY_JNI_UTIL_GET_METHOD(env, cls, limitId, "limit", "()I", done); + + // Try to get the ids of the position and limit fields. We later then check if we was able + // to find them and if so use them get the position and limit of the buffer. This is + // much faster then call back into java via (*env)->CallIntMethod(...). + NETTY_JNI_UTIL_TRY_GET_FIELD(env, cls, posFieldId, "position", "I"); + NETTY_JNI_UTIL_TRY_GET_FIELD(env, cls, limitFieldId, "limit", "I"); + + ret = NETTY_JNI_UTIL_JNI_VERSION; +done: + free(mem); + return ret; +} + +void netty_unix_filedescriptor_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix) { + netty_jni_util_unregister_natives(env, packagePrefix, FILEDESCRIPTOR_CLASSNAME); +} diff --git a/netty-channel-unix-native/src/main/c/netty_unix_limits.c b/netty-channel-unix-native/src/main/c/netty_unix_limits.c new file mode 100644 index 0000000..ae0e63b --- /dev/null +++ b/netty-channel-unix-native/src/main/c/netty_unix_limits.c @@ -0,0 +1,89 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include "netty_unix_jni.h" +#include "netty_unix_limits.h" +#include "netty_unix_util.h" +#include "netty_jni_util.h" + +#define LIMITS_CLASSNAME "io/netty/channel/unix/LimitsStaticallyReferencedJniMethods" + +// Define IOV_MAX if not found to limit the iov size on writev calls +// See https://github.com/netty/netty/issues/2647 +#ifndef IOV_MAX +#define IOV_MAX 1024 +#endif /* IOV_MAX */ + +// Define UIO_MAXIOV if not found +#ifndef UIO_MAXIOV +#define UIO_MAXIOV 1024 +#endif /* UIO_MAXIOV */ + +// JNI Registered Methods Begin +static jlong netty_unix_limits_ssizeMax(JNIEnv* env, jclass clazz) { + return SSIZE_MAX; +} + +static jint netty_unix_limits_iovMax(JNIEnv* env, jclass clazz) { + return IOV_MAX; +} + +static jint netty_unix_limits_uioMaxIov(JNIEnv* env, jclass clazz) { + return UIO_MAXIOV; +} + +static jint netty_unix_limits_sizeOfjlong(JNIEnv* env, jclass clazz) { + return sizeof(jlong); +} + +static jint netty_unix_limits_udsSunPathSize(JNIEnv* env, jclass clazz) { + struct sockaddr_un udsAddr; + return sizeof(udsAddr.sun_path) / sizeof(udsAddr.sun_path[0]); +} +// JNI Registered Methods End + +// JNI Method Registration Table Begin +static const JNINativeMethod statically_referenced_fixed_method_table[] = { + { "ssizeMax", "()J", (void *) netty_unix_limits_ssizeMax }, + { "iovMax", "()I", (void *) netty_unix_limits_iovMax }, + { "uioMaxIov", "()I", (void *) netty_unix_limits_uioMaxIov }, + { "sizeOfjlong", "()I", (void *) netty_unix_limits_sizeOfjlong }, + { "udsSunPathSize", "()I", (void *) netty_unix_limits_udsSunPathSize } +}; +static const jint statically_referenced_fixed_method_table_size = sizeof(statically_referenced_fixed_method_table) / sizeof(statically_referenced_fixed_method_table[0]); +// JNI Method Registration Table End + +// IMPORTANT: If you add any NETTY_JNI_UTIL_LOAD_CLASS or NETTY_JNI_UTIL_FIND_CLASS calls you also need to update +// Unix to reflect that. +jint netty_unix_limits_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { + // We must register the statically referenced methods first! + if (netty_jni_util_register_natives(env, + packagePrefix, + LIMITS_CLASSNAME, + statically_referenced_fixed_method_table, + statically_referenced_fixed_method_table_size) != 0) { + return JNI_ERR; + } + + return NETTY_JNI_UTIL_JNI_VERSION; +} + +void netty_unix_limits_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix) { + netty_jni_util_unregister_natives(env, packagePrefix, LIMITS_CLASSNAME); +} diff --git a/netty-channel-unix-native/src/main/c/netty_unix_socket.c b/netty-channel-unix-native/src/main/c/netty_unix_socket.c new file mode 100644 index 0000000..2e4defe --- /dev/null +++ b/netty-channel-unix-native/src/main/c/netty_unix_socket.c @@ -0,0 +1,1385 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "netty_unix_errors.h" +#include "netty_unix_jni.h" +#include "netty_unix_socket.h" +#include "netty_unix_util.h" +#include "netty_jni_util.h" + +#define SOCKET_CLASSNAME "io/netty/channel/unix/Socket" +// Define SO_REUSEPORT if not found to fix build issues. +// See https://github.com/netty/netty/issues/2558 +#ifndef SO_REUSEPORT +#define SO_REUSEPORT 15 +#endif /* SO_REUSEPORT */ + +// MSG_FASTOPEN is defined in linux 3.6. We define this here so older kernels can compile. +#ifndef MSG_FASTOPEN +#define MSG_FASTOPEN 0x20000000 +#endif + +static jweak datagramSocketAddressClassWeak = NULL; +static jweak domainDatagramSocketAddressClassWeak = NULL; +static jmethodID datagramSocketAddrMethodId = NULL; +static jmethodID domainDatagramSocketAddrMethodId = NULL; +static jmethodID inetSocketAddrMethodId = NULL; +static jclass inetSocketAddressClass = NULL; +static const unsigned char wildcardAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const unsigned char ipv4MappedWildcardAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 }; +static const unsigned char ipv4MappedAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }; + +// Optional external methods +extern int accept4(int sockFd, struct sockaddr* addr, socklen_t* addrlen, int flags) __attribute__((weak)) __attribute__((weak_import)); + +// macro to calculate the length of a sockaddr_un struct for a given path length. +// see sys/un.h#SUN_LEN, this is modified to allow nul bytes +#define _UNIX_ADDR_LENGTH(path_len) ((uintptr_t) offsetof(struct sockaddr_un, sun_path) + (uintptr_t) path_len) + +int netty_unix_socket_nonBlockingSocket(int domain, int type, int protocol) { +#ifdef SOCK_NONBLOCK + return socket(domain, type | SOCK_NONBLOCK, protocol); +#else + int socketFd = socket(domain, type, protocol); + int flags; + // Don't initialize flags until we know the socket is good so errno is preserved. + if (socketFd < 0 || + (flags = fcntl(socketFd, F_GETFL, 0)) < 0 || + fcntl(socketFd, F_SETFL, flags | O_NONBLOCK) < 0) { + return -1; + } + return socketFd; +#endif +} + +int netty_unix_socket_ipAddressLength(const struct sockaddr_storage* addr) { + if (addr->ss_family == AF_INET) { + return 4; + } + struct sockaddr_in6* s = (struct sockaddr_in6*) addr; + if (memcmp(s->sin6_addr.s6_addr, ipv4MappedAddress, 12) == 0) { + // IPv4-mapped-on-IPv6 + return 4; + } + return 16; +} + +static jobject createDatagramSocketAddress(JNIEnv* env, const struct sockaddr_storage* addr, int len, jobject local) { + jclass datagramSocketAddressClass = NULL; + jobject obj = NULL; + int port; + int scopeId; + int ipLength = netty_unix_socket_ipAddressLength(addr); + jbyteArray addressBytes = (*env)->NewByteArray(env, ipLength); + if (addressBytes == NULL) { + return NULL; + } + if (addr->ss_family == AF_INET) { + struct sockaddr_in* s = (struct sockaddr_in*) addr; + port = ntohs(s->sin_port); + scopeId = 0; + (*env)->SetByteArrayRegion(env, addressBytes, 0, ipLength, (jbyte*) &s->sin_addr.s_addr); + } else { + struct sockaddr_in6* s = (struct sockaddr_in6*) addr; + port = ntohs(s->sin6_port); + scopeId = s->sin6_scope_id; + + int offset; + if (ipLength == 4) { + // IPv4-mapped-on-IPv6. + offset = 12; + } else { + offset = 0; + } + jbyte* addr = (jbyte*) &s->sin6_addr.s6_addr; + (*env)->SetByteArrayRegion(env, addressBytes, 0, ipLength, addr + offset); + } + + NETTY_JNI_UTIL_NEW_LOCAL_FROM_WEAK(env, datagramSocketAddressClass, datagramSocketAddressClassWeak, done); + + obj = (*env)->NewObject(env, datagramSocketAddressClass, datagramSocketAddrMethodId, addressBytes, scopeId, port, len, local); + if ((*env)->ExceptionCheck(env) == JNI_TRUE) { + obj = NULL; + goto done; + } +done: + NETTY_JNI_UTIL_DELETE_LOCAL(env, datagramSocketAddressClass); + + return obj; +} + +static jobject createDomainDatagramSocketAddress(JNIEnv* env, const struct sockaddr_storage* addr, int len, jobject local) { + jclass domainDatagramSocketAddressClass = NULL; + jobject obj = NULL; + struct sockaddr_un* s = (struct sockaddr_un*) addr; + int pathLength = strlen(s->sun_path); + jbyteArray pathBytes = (*env)->NewByteArray(env, pathLength); + if (pathBytes == NULL) { + return NULL; + } + + (*env)->SetByteArrayRegion(env, pathBytes, 0, pathLength, (jbyte*) &s->sun_path); + + NETTY_JNI_UTIL_NEW_LOCAL_FROM_WEAK(env, domainDatagramSocketAddressClass, domainDatagramSocketAddressClassWeak, done); + + obj = (*env)->NewObject(env, domainDatagramSocketAddressClass, domainDatagramSocketAddrMethodId, pathBytes, len, local); + if ((*env)->ExceptionCheck(env) == JNI_TRUE) { + return NULL; + } +done: + NETTY_JNI_UTIL_DELETE_LOCAL(env, domainDatagramSocketAddressClass); + + return obj; +} + +static jbyteArray netty_unix_socket_createDomainSocketAddressArray(JNIEnv* env, const struct sockaddr_storage* addr) { + struct sockaddr_un* s = (struct sockaddr_un*) addr; + int pathLength = strlen(s->sun_path); + jbyteArray pathBytes = (*env)->NewByteArray(env, pathLength); + if (pathBytes == NULL) { + return NULL; + } + + (*env)->SetByteArrayRegion(env, pathBytes, 0, pathLength, (jbyte*) &s->sun_path); + return pathBytes; +} + +static jsize addressLength(const struct sockaddr_storage* addr) { + int len = netty_unix_socket_ipAddressLength(addr); + if (len == 4) { + // Only encode port into it + return len + 4; + } + // we encode port + scope into it + return len + 8; +} + +static void initInetSocketAddressArray(JNIEnv* env, const struct sockaddr_storage* addr, jbyteArray bArray, int offset, jsize len) { + int port; + if (addr->ss_family == AF_INET) { + struct sockaddr_in* s = (struct sockaddr_in*) addr; + port = ntohs(s->sin_port); + + // Encode address and port into the array + unsigned char a[4]; + a[0] = port >> 24; + a[1] = port >> 16; + a[2] = port >> 8; + a[3] = port; + (*env)->SetByteArrayRegion(env, bArray, offset, 4, (jbyte*) &s->sin_addr.s_addr); + (*env)->SetByteArrayRegion(env, bArray, offset + 4, 4, (jbyte*) &a); + } else { + struct sockaddr_in6* s = (struct sockaddr_in6*) addr; + port = ntohs(s->sin6_port); + + if (len == 8) { + // IPv4-mapped-on-IPv6 + // Encode port into the array and write it into the jbyteArray + unsigned char a[4]; + a[0] = port >> 24; + a[1] = port >> 16; + a[2] = port >> 8; + a[3] = port; + + // we only need the last 4 bytes for mapped address + (*env)->SetByteArrayRegion(env, bArray, offset, 4, (jbyte*) &(s->sin6_addr.s6_addr[12])); + (*env)->SetByteArrayRegion(env, bArray, offset + 4, 4, (jbyte*) &a); + } else { + // Encode scopeid and port into the array + unsigned char a[8]; + a[0] = s->sin6_scope_id >> 24; + a[1] = s->sin6_scope_id >> 16; + a[2] = s->sin6_scope_id >> 8; + a[3] = s->sin6_scope_id; + a[4] = port >> 24; + a[5] = port >> 16; + a[6] = port >> 8; + a[7] = port; + + (*env)->SetByteArrayRegion(env, bArray, offset, 16, (jbyte*) &(s->sin6_addr.s6_addr)); + (*env)->SetByteArrayRegion(env, bArray, offset + 16, 8, (jbyte*) &a); + } + } +} + +jbyteArray netty_unix_socket_createInetSocketAddressArray(JNIEnv* env, const struct sockaddr_storage* addr) { + jsize len = addressLength(addr); + jbyteArray bArray = (*env)->NewByteArray(env, len); + if (bArray == NULL) { + return NULL; + } + initInetSocketAddressArray(env, addr, bArray, 0, len); + return bArray; +} + +static jboolean netty_unix_socket_isIPv6Preferred0(JNIEnv* env, jclass clazz, jboolean ipv4Preferred) { + if (ipv4Preferred) { + // User asked to use ipv4 explicitly. + return JNI_FALSE; + } + + int fd = netty_unix_socket_nonBlockingSocket(AF_INET6, SOCK_STREAM, 0); + if (fd == -1) { + return errno == EAFNOSUPPORT ? JNI_FALSE : JNI_TRUE; + } + + // Explicitly try to bind to ::1 to ensure IPV6 can really be used. + // See https://github.com/netty/netty/issues/7021. + struct sockaddr_in6 addr; + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_addr.s6_addr[15] = 1; /* [::1]:0 */ + int res = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + + close(fd); + return res == 0 ? JNI_TRUE : JNI_FALSE; +} + + +static jboolean netty_unix_socket_isIPv6(JNIEnv* env, jclass clazz, jint fd) { + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + if (getsockname(fd, (struct sockaddr*) &addr, &addrlen) == 0) { + return ((struct sockaddr*) &addr)->sa_family == AF_INET6; + } + + netty_unix_errors_throwChannelExceptionErrorNo(env, "getsockname(...) failed: ", errno); + return JNI_FALSE; +} + +static void netty_unix_socket_optionHandleError(JNIEnv* env, int err, char* method) { + if (err == EBADF) { + netty_unix_errors_throwClosedChannelException(env); + } else { + netty_unix_errors_throwChannelExceptionErrorNo(env, method, err); + } +} + +static void netty_unix_socket_setOptionHandleError(JNIEnv* env, int err) { + netty_unix_socket_optionHandleError(env, err, "setsockopt() failed: "); +} + +static int netty_unix_socket_setOption0(jint fd, int level, int optname, const void* optval, socklen_t len) { + return setsockopt(fd, level, optname, optval, len); +} + +static jint _socket(JNIEnv* env, jclass clazz, int domain, int type) { + int fd = netty_unix_socket_nonBlockingSocket(domain, type, 0); + if (fd == -1) { + return -errno; + } else if (domain == AF_INET6) { + // Try to allow listen /connect ipv4 and ipv6 + int optval = 0; + if (netty_unix_socket_setOption0(fd, IPPROTO_IPV6, IPV6_V6ONLY, &optval, sizeof(optval)) < 0) { + if (errno != EAFNOSUPPORT) { + netty_unix_socket_setOptionHandleError(env, errno); + // Something went wrong so close the fd and return here. setOption(...) itself throws the exception already. + close(fd); + return -1; + } + // else we failed to enable dual stack mode. + // It is assumed the socket is re‐stricted to sending and receiving IPv6 packets only. + // Don't close fd and don't return -1. At best we can do is log. + // TODO: bubble this up to an actual Logger. + } + } + return fd; +} + +int netty_unix_socket_initSockaddr(JNIEnv* env, jboolean ipv6, jbyteArray address, jint scopeId, jint jport, + const struct sockaddr_storage* addr, socklen_t* addrSize) { + uint16_t port = htons((uint16_t) jport); + // We use 16 bytes as this allows us to fit ipv6, ipv4 and ipv4 mapped ipv6 addresses in the array. + jbyte addressBytes[16]; + + int len = (*env)->GetArrayLength(env, address); + + if (len > 16) { + // This should never happen but let's guard against it anyway. + return -1; + } + + // We use GetByteArrayRegion(...) and copy into a small stack allocated buffer and NOT GetPrimitiveArrayCritical(...) + // as there are still multiple GCLocker related bugs which are not fixed yet. + // + // For example: + // https://bugs.openjdk.java.net/browse/JDK-8048556 + // https://bugs.openjdk.java.net/browse/JDK-8057573 + // https://bugs.openjdk.java.net/browse/JDK-8057586 + (*env)->GetByteArrayRegion(env, address, 0, len, addressBytes); + + if (ipv6 == JNI_TRUE) { + struct sockaddr_in6* ip6addr = (struct sockaddr_in6*) addr; + *addrSize = sizeof(struct sockaddr_in6); + ip6addr->sin6_family = AF_INET6; + ip6addr->sin6_port = port; + ip6addr->sin6_flowinfo = 0; + ip6addr->sin6_scope_id = (uint32_t) scopeId; + // check if this is an any address and if so we need to handle it like this. + if (memcmp(addressBytes, wildcardAddress, 16) == 0 || memcmp(addressBytes, ipv4MappedWildcardAddress, 16) == 0) { + ip6addr->sin6_addr = in6addr_any; + } else { + memcpy(&(ip6addr->sin6_addr.s6_addr), addressBytes, 16); + } + } else { + struct sockaddr_in* ipaddr = (struct sockaddr_in*) addr; + *addrSize = sizeof(struct sockaddr_in); + ipaddr->sin_family = AF_INET; + ipaddr->sin_port = port; + memcpy(&(ipaddr->sin_addr.s_addr), addressBytes + 12, 4); + } + return 0; +} + +static jint _sendTo(JNIEnv* env, jint fd, jboolean ipv6, void* buffer, jint pos, jint limit, jbyteArray address, jint scopeId, jint port, jint flags) { + struct sockaddr_storage addr; + socklen_t addrSize; + if (netty_unix_socket_initSockaddr(env, ipv6, address, scopeId, port, &addr, &addrSize) == -1) { + return -1; + } + + ssize_t res; + int err; + do { + res = sendto(fd, buffer + pos, (size_t) (limit - pos), flags, (struct sockaddr*) &addr, addrSize); + // keep on writing if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + return -err; + } + return (jint) res; +} + +static jint _sendToDomainSocket(JNIEnv* env, jint fd, void* buffer, jint pos, jint limit, jbyteArray socketPath) { + struct sockaddr_un addr; + jint socket_path_len; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + jbyte* socket_path = (*env)->GetByteArrayElements(env, socketPath, 0); + socket_path_len = (*env)->GetArrayLength(env, socketPath); + if (socket_path_len > sizeof(addr.sun_path)) { + socket_path_len = sizeof(addr.sun_path); + } + memcpy(addr.sun_path, socket_path, socket_path_len); + + ssize_t res; + int err; + do { + res = sendto(fd, buffer + pos, (size_t) (limit - pos), 0, (struct sockaddr*) &addr, _UNIX_ADDR_LENGTH(socket_path_len)); + // keep on writing if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + (*env)->ReleaseByteArrayElements(env, socketPath, socket_path, 0); + + if (res < 0) { + return -err; + } + return (jint) res; +} + +static jobject _recvFrom(JNIEnv* env, jint fd, void* buffer, jint pos, jint limit) { + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + ssize_t res; + int err; + + do { + res = recvfrom(fd, buffer + pos, (size_t) (limit - pos), 0, (struct sockaddr*) &addr, &addrlen); + // Keep on reading if we was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + if (err == EAGAIN || err == EWOULDBLOCK) { + // Nothing left to read + return NULL; + } + if (err == EBADF) { + netty_unix_errors_throwClosedChannelException(env); + return NULL; + } + if (err == ECONNREFUSED) { + netty_unix_errors_throwPortUnreachableException(env, "recvfrom() failed"); + return NULL; + } + netty_unix_errors_throwIOExceptionErrorNo(env, "recvfrom() failed: ", err); + return NULL; + } + + return createDatagramSocketAddress(env, &addr, res, NULL); +} + +static jobject _recvFromDomainSocket(JNIEnv* env, jint fd, void* buffer, jint pos, jint limit) { + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + ssize_t res; + int err; + + do { + bzero(&addr, sizeof(addr)); // Zap addr so we can strlen(addr.sun_path) later. See unix(4). + res = recvfrom(fd, buffer + pos, (size_t) (limit - pos), 0, (struct sockaddr*) &addr, &addrlen); + // Keep on reading if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + if (err == EAGAIN || err == EWOULDBLOCK) { + // Nothing left to read + return NULL; + } + if (err == EBADF) { + netty_unix_errors_throwClosedChannelException(env); + return NULL; + } + netty_unix_errors_throwIOExceptionErrorNo(env, "_recvFromDomainSocket() failed: ", err); + return NULL; + } + + return createDomainDatagramSocketAddress(env, &addr, res, NULL); +} + +static jint _send(JNIEnv* env, jclass clazz, jint fd, void* buffer, jint pos, jint limit) { + ssize_t res; + int err; + do { + res = send(fd, buffer + pos, (size_t) (limit - pos), 0); + // keep on writing if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + return -err; + } + return (jint) res; +} + +static jint netty_unix_socket_send(JNIEnv* env, jclass clazz, jint fd, jobject jbuffer, jint pos, jint limit) { + // We check that GetDirectBufferAddress will not return NULL in OnLoad + return _send(env, clazz, fd, (*env)->GetDirectBufferAddress(env, jbuffer), pos, limit); +} + +static jint netty_unix_socket_sendAddress(JNIEnv* env, jclass clazz, jint fd, jlong address, jint pos, jint limit) { + return _send(env, clazz, fd, (void*) (intptr_t) address, pos, limit); +} + +static jint _recv(JNIEnv* env, jclass clazz, jint fd, void* buffer, jint pos, jint limit) { + ssize_t res; + int err; + do { + res = recv(fd, buffer + pos, (size_t) (limit - pos), 0); + // Keep on reading if we was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + return -err; + } + return (jint) res; +} + +static jint netty_unix_socket_recv(JNIEnv* env, jclass clazz, jint fd, jobject jbuffer, jint pos, jint limit) { + // We check that GetDirectBufferAddress will not return NULL in OnLoad + return _recv(env, clazz, fd, (*env)->GetDirectBufferAddress(env, jbuffer), pos, limit); +} + +static jint netty_unix_socket_recvAddress(JNIEnv* env, jclass clazz, jint fd, jlong address, jint pos, jint limit) { + return _recv(env, clazz, fd, (void*) (intptr_t) address, pos, limit); +} + +void netty_unix_socket_getOptionHandleError(JNIEnv* env, int err) { + netty_unix_socket_optionHandleError(env, err, "getsockopt() failed: "); +} + +int netty_unix_socket_getOption0(jint fd, int level, int optname, void* optval, socklen_t optlen) { + return getsockopt(fd, level, optname, optval, &optlen); +} + +int netty_unix_socket_getOption(JNIEnv* env, jint fd, int level, int optname, void* optval, socklen_t optlen) { + int rc = netty_unix_socket_getOption0(fd, level, optname, optval, optlen); + if (rc < 0) { + netty_unix_socket_getOptionHandleError(env, errno); + } + return rc; +} + +int netty_unix_socket_setOption(JNIEnv* env, jint fd, int level, int optname, const void* optval, socklen_t len) { + int rc = netty_unix_socket_setOption0(fd, level, optname, optval, len); + if (rc < 0) { + netty_unix_socket_setOptionHandleError(env, errno); + } + return rc; +} + +// JNI Registered Methods Begin +static jint netty_unix_socket_shutdown(JNIEnv* env, jclass clazz, jint fd, jboolean read, jboolean write) { + int mode; + if (read && write) { + mode = SHUT_RDWR; + } else if (read) { + mode = SHUT_RD; + } else if (write) { + mode = SHUT_WR; + } else { + return -EINVAL; + } + if (shutdown(fd, mode) < 0) { + return -errno; + } + return 0; +} + +static jint netty_unix_socket_bind(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jbyteArray address, jint scopeId, jint port) { + struct sockaddr_storage addr; + socklen_t addrSize; + if (netty_unix_socket_initSockaddr(env, ipv6, address, scopeId, port, &addr, &addrSize) == -1) { + return -1; + } + + if (bind(fd, (struct sockaddr*) &addr, addrSize) == -1) { + return -errno; + } + return 0; +} + +static jint netty_unix_socket_listen(JNIEnv* env, jclass clazz, jint fd, jint backlog) { + if (listen(fd, backlog) == -1) { + return -errno; + } + return 0; +} + +static jint netty_unix_socket_connect(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jbyteArray address, jint scopeId, jint port) { + struct sockaddr_storage addr; + socklen_t addrSize; + if (netty_unix_socket_initSockaddr(env, ipv6, address, scopeId, port, &addr, &addrSize) == -1) { + // A runtime exception was thrown + return -1; + } + + int res; + int err; + do { + res = connect(fd, (struct sockaddr*) &addr, addrSize); + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + return -err; + } + return 0; +} + +static jint netty_unix_socket_finishConnect(JNIEnv* env, jclass clazz, jint fd) { + // connect may be done + // return true if connection finished successfully + // return false if connection is still in progress + // throw exception if connection failed + int optval; + int res = netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_ERROR, &optval, sizeof(optval)); + if (res != 0) { + // getOption failed + return -1; + } + if (optval == 0) { + // connect succeeded + return 0; + } + return -optval; +} + +static jint netty_unix_socket_disconnect(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6) { +#ifndef __APPLE__ + struct sockaddr_storage addr; + int len; + + memset(&addr, 0, sizeof(addr)); + // You can disconnect connection-less sockets by using AF_UNSPEC. + // See man 2 connect. + if (ipv6 == JNI_TRUE) { + struct sockaddr_in6* ip6addr = (struct sockaddr_in6*) &addr; + ip6addr->sin6_family = AF_UNSPEC; + len = sizeof(struct sockaddr_in6); + } else { + struct sockaddr_in* ipaddr = (struct sockaddr_in*) &addr; + ipaddr->sin_family = AF_UNSPEC; + len = sizeof(struct sockaddr_in); + } +#endif // __APPLE__ + + int res; + int err; + do { +#ifdef __APPLE__ + res = disconnectx(fd, SAE_ASSOCID_ANY, SAE_CONNID_ANY); +#else + res = connect(fd, (struct sockaddr*) &addr, len); +#endif // __APPLE__ + } while (res == -1 && ((err = errno) == EINTR)); + + // EAFNOSUPPORT is harmless in this case. + // See https://www.unix.com/man-page/osx/2/connect/ + if (res < 0 && err != EAFNOSUPPORT) { + return -err; + } + return 0; +} + +static jint netty_unix_socket_accept(JNIEnv* env, jclass clazz, jint fd, jbyteArray acceptedAddress) { + jint socketFd; + jsize len; + jbyte len_b; + int err; + struct sockaddr_storage addr; + socklen_t address_len = sizeof(addr); + + for (;;) { +#ifdef SOCK_NONBLOCK + if (accept4) { + socketFd = accept4(fd, (struct sockaddr*) &addr, &address_len, SOCK_NONBLOCK | SOCK_CLOEXEC); + } else { +#endif + socketFd = accept(fd, (struct sockaddr*) &addr, &address_len); +#ifdef SOCK_NONBLOCK + } +#endif + + if (socketFd != -1) { + break; + } + if ((err = errno) != EINTR) { + return -err; + } + } + + len = addressLength(&addr); + len_b = (jbyte) len; + + // Fill in remote address details + (*env)->SetByteArrayRegion(env, acceptedAddress, 0, 1, (jbyte*) &len_b); + initInetSocketAddressArray(env, &addr, acceptedAddress, 1, len); + + if (accept4) { + return socketFd; + } + if (fcntl(socketFd, F_SETFD, FD_CLOEXEC) == -1 || fcntl(socketFd, F_SETFL, O_NONBLOCK) == -1) { + // accept4 was not present so need two more sys-calls ... + return -errno; + } + return socketFd; +} + +static jbyteArray netty_unix_socket_remoteAddress(JNIEnv* env, jclass clazz, jint fd) { + struct sockaddr_storage addr = { 0 }; + socklen_t len = sizeof(addr); + if (getpeername(fd, (struct sockaddr*) &addr, &len) == -1) { + return NULL; + } + return netty_unix_socket_createInetSocketAddressArray(env, &addr); +} + +static jbyteArray netty_unix_socket_remoteDomainSocketAddress(JNIEnv* env, jclass clazz, jint fd) { + struct sockaddr_storage addr = { 0 }; + socklen_t len = sizeof(addr); + if (getpeername(fd, (struct sockaddr*) &addr, &len) == -1) { + return NULL; + } + return netty_unix_socket_createDomainSocketAddressArray(env, &addr); +} + +static jbyteArray netty_unix_socket_localAddress(JNIEnv* env, jclass clazz, jint fd) { + struct sockaddr_storage addr = { 0 }; + socklen_t len = sizeof(addr); + if (getsockname(fd, (struct sockaddr*) &addr, &len) == -1) { + return NULL; + } + return netty_unix_socket_createInetSocketAddressArray(env, &addr); +} + +static jbyteArray netty_unix_socket_localDomainSocketAddress(JNIEnv* env, jclass clazz, jint fd) { + struct sockaddr_storage addr = { 0 }; + socklen_t len = sizeof(addr); + if (getsockname(fd, (struct sockaddr*) &addr, &len) == -1) { + return NULL; + } + return netty_unix_socket_createDomainSocketAddressArray(env, &addr); +} + +static jint netty_unix_socket_newSocketDgramFd(JNIEnv* env, jclass clazz, jboolean ipv6) { + int domain = ipv6 == JNI_TRUE ? AF_INET6 : AF_INET; + return _socket(env, clazz, domain, SOCK_DGRAM); +} + +static jint netty_unix_socket_newSocketStreamFd(JNIEnv* env, jclass clazz, jboolean ipv6) { + int domain = ipv6 == JNI_TRUE ? AF_INET6 : AF_INET; + return _socket(env, clazz, domain, SOCK_STREAM); +} + +static jint netty_unix_socket_newSocketDomainFd(JNIEnv* env, jclass clazz) { + int fd = netty_unix_socket_nonBlockingSocket(PF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + return -errno; + } + return fd; +} + +static jint netty_unix_socket_newSocketDomainDgramFd(JNIEnv* env, jclass clazz) { + int fd = netty_unix_socket_nonBlockingSocket(PF_UNIX, SOCK_DGRAM, 0); + if (fd == -1) { + return -errno; + } + return fd; +} + +static jint netty_unix_socket_sendTo(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jobject jbuffer, jint pos, jint limit, jbyteArray address, jint scopeId, jint port, jint flags) { + // We check that GetDirectBufferAddress will not return NULL in OnLoad + return _sendTo(env, fd, ipv6, (*env)->GetDirectBufferAddress(env, jbuffer), pos, limit, address, scopeId, port, flags); +} + +static jint netty_unix_socket_sendToAddress(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jlong memoryAddress, jint pos, jint limit, jbyteArray address, jint scopeId, jint port, jint flags) { + return _sendTo(env, fd, ipv6, (void *) (intptr_t) memoryAddress, pos, limit, address, scopeId, port, flags); +} + +static jint netty_unix_socket_sendToAddresses(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jlong memoryAddress, jint length, jbyteArray address, jint scopeId, jint port, jint flags) { + struct sockaddr_storage addr; + socklen_t addrSize; + if (netty_unix_socket_initSockaddr(env, ipv6, address, scopeId, port, &addr, &addrSize) == -1) { + return -1; + } + + struct msghdr m = { 0 }; + m.msg_name = (void*) &addr; + m.msg_namelen = addrSize; + m.msg_iov = (struct iovec*) (intptr_t) memoryAddress; + m.msg_iovlen = length; + + ssize_t res; + int err; + do { + res = sendmsg(fd, &m, flags); + // keep on writing if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + return -err; + } + return (jint) res; +} + +static jint netty_unix_socket_sendToDomainSocket(JNIEnv* env, jclass clazz, jint fd, jobject jbuffer, jint pos, jint limit, jbyteArray socketPath) { + // We check that GetDirectBufferAddress will not return NULL in OnLoad + return _sendToDomainSocket(env, fd, (*env)->GetDirectBufferAddress(env, jbuffer), pos, limit, socketPath); +} + +static jint netty_unix_socket_sendToAddressDomainSocket(JNIEnv* env, jclass clazz, jint fd, jlong memoryAddress, jint pos, jint limit, jbyteArray socketPath) { + return _sendToDomainSocket(env, fd, (void *) (intptr_t) memoryAddress, pos, limit, socketPath); +} + +static jint netty_unix_socket_sendToAddressesDomainSocket(JNIEnv* env, jclass clazz, jint fd, jlong memoryAddress, jint length, jbyteArray socketPath) { + struct sockaddr_un addr; + jint socket_path_len; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + jbyte* socket_path = (*env)->GetByteArrayElements(env, socketPath, 0); + socket_path_len = (*env)->GetArrayLength(env, socketPath); + if (socket_path_len > sizeof(addr.sun_path)) { + socket_path_len = sizeof(addr.sun_path); + } + memcpy(addr.sun_path, socket_path, socket_path_len); + + struct msghdr m = { 0 }; + m.msg_name = (void*) &addr; + m.msg_namelen = sizeof(struct sockaddr_un); + m.msg_iov = (struct iovec*) (intptr_t) memoryAddress; + m.msg_iovlen = length; + + ssize_t res; + int err; + do { + res = sendmsg(fd, &m, 0); + // keep on writing if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + (*env)->ReleaseByteArrayElements(env, socketPath, socket_path, 0); + + if (res < 0) { + return -err; + } + return (jint) res; +} + +static jobject netty_unix_socket_recvFrom(JNIEnv* env, jclass clazz, jint fd, jobject jbuffer, jint pos, jint limit) { + // We check that GetDirectBufferAddress will not return NULL in OnLoad + return _recvFrom(env, fd, (*env)->GetDirectBufferAddress(env, jbuffer), pos, limit); +} + +static jobject netty_unix_socket_recvFromAddress(JNIEnv* env, jclass clazz, jint fd, jlong address, jint pos, jint limit) { + return _recvFrom(env, fd, (void *) (intptr_t) address, pos, limit); +} + +static jobject netty_unix_socket_recvFromDomainSocket(JNIEnv* env, jclass clazz, jint fd, jobject jbuffer, jint pos, jint limit) { + // We check that GetDirectBufferAddress will not return NULL in OnLoad + return _recvFromDomainSocket(env, fd, (*env)->GetDirectBufferAddress(env, jbuffer), pos, limit); +} + +static jobject netty_unix_socket_recvFromAddressDomainSocket(JNIEnv* env, jclass clazz, jint fd, jlong address, jint pos, jint limit) { + return _recvFromDomainSocket(env, fd, (void *) (intptr_t) address, pos, limit); +} + +static jint netty_unix_socket_bindDomainSocket(JNIEnv* env, jclass clazz, jint fd, jbyteArray socketPath) { + struct sockaddr_un addr; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + jbyte* socket_path = (*env)->GetByteArrayElements(env, socketPath, 0); + jint socket_path_len = (*env)->GetArrayLength(env, socketPath); + + if (socket_path_len > sizeof(addr.sun_path) || (socket_path_len == sizeof(addr.sun_path) && socket_path[socket_path_len] != '\0')) { + (*env)->ReleaseByteArrayElements(env, socketPath, socket_path, 0); + return -ENAMETOOLONG; + } + memcpy(addr.sun_path, socket_path, socket_path_len); + (*env)->ReleaseByteArrayElements(env, socketPath, socket_path, 0); + + if (unlink((const char*) addr.sun_path) == -1 && errno != ENOENT) { + return -errno; + } + + int res = bind(fd, (struct sockaddr*) &addr, _UNIX_ADDR_LENGTH(socket_path_len)); + + if (res == -1) { + return -errno; + } + return res; +} + +static jint netty_unix_socket_connectDomainSocket(JNIEnv* env, jclass clazz, jint fd, jbyteArray socketPath) { + struct sockaddr_un addr; + jint socket_path_len; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + jbyte* socket_path = (*env)->GetByteArrayElements(env, socketPath, 0); + socket_path_len = (*env)->GetArrayLength(env, socketPath); + + if (socket_path_len > sizeof(addr.sun_path) || (socket_path_len == sizeof(addr.sun_path) && socket_path[socket_path_len] != '\0')) { + (*env)->ReleaseByteArrayElements(env, socketPath, socket_path, 0); + return -ENAMETOOLONG; + } + memcpy(addr.sun_path, socket_path, socket_path_len); + (*env)->ReleaseByteArrayElements(env, socketPath, socket_path, 0); + + int res; + int err; + do { + res = connect(fd, (struct sockaddr*) &addr, _UNIX_ADDR_LENGTH(socket_path_len)); + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + return -err; + } + return 0; +} + +static jint netty_unix_socket_recvFd(JNIEnv* env, jclass clazz, jint fd) { + int socketFd; + struct msghdr descriptorMessage = { 0 }; + struct iovec iov[1] = { { 0 } }; + char control[CMSG_SPACE(sizeof(int))] = { 0 }; + char iovecData[1]; + + descriptorMessage.msg_control = control; + descriptorMessage.msg_controllen = sizeof(control); + descriptorMessage.msg_iov = iov; + descriptorMessage.msg_iovlen = 1; + iov[0].iov_base = iovecData; + iov[0].iov_len = sizeof(iovecData); + + ssize_t res; + int err; + + for (;;) { + do { + res = recvmsg(fd, &descriptorMessage, 0); + // Keep on reading if we was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res == 0) { + return 0; + } + + if (res < 0) { + return -err; + } + + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&descriptorMessage); + if (!cmsg) { + return -errno; + } + + if ((cmsg->cmsg_len == CMSG_LEN(sizeof(int))) && (cmsg->cmsg_level == SOL_SOCKET) && (cmsg->cmsg_type == SCM_RIGHTS)) { + socketFd = *((int *) CMSG_DATA(cmsg)); + // set as non blocking as we want to use it with kqueue/epoll + if (fcntl(socketFd, F_SETFL, O_NONBLOCK) == -1) { + err = errno; + close(socketFd); + return -err; + } + return socketFd; + } + } +} + +static jint netty_unix_socket_sendFd(JNIEnv* env, jclass clazz, jint socketFd, jint fd) { + struct msghdr descriptorMessage = { 0 }; + struct iovec iov[1] = { { 0 } }; + char control[CMSG_SPACE(sizeof(int))] = { 0 }; + char iovecData[1]; + + descriptorMessage.msg_control = control; + descriptorMessage.msg_controllen = sizeof(control); + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&descriptorMessage); + + if (cmsg) { + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *((int *)CMSG_DATA(cmsg)) = fd; + descriptorMessage.msg_iov = iov; + descriptorMessage.msg_iovlen = 1; + iov[0].iov_base = iovecData; + iov[0].iov_len = sizeof(iovecData); + + ssize_t res; + int err; + do { + res = sendmsg(socketFd, &descriptorMessage, 0); + // keep on writing if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + return -err; + } + return (jint) res; + } + return -1; +} + +static void netty_unix_socket_setReuseAddress(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); +} + +static void netty_unix_socket_setReusePort(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); +} + +static void netty_unix_socket_setTcpNoDelay(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)); +} + +static void netty_unix_socket_setReceiveBufferSize(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_RCVBUF, &optval, sizeof(optval)); +} + +static void netty_unix_socket_setSendBufferSize(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_SNDBUF, &optval, sizeof(optval)); +} + +static void netty_unix_socket_setKeepAlive(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)); +} + +static void netty_unix_socket_setBroadcast(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)); +} + +static void netty_unix_socket_setSoLinger(JNIEnv* env, jclass clazz, jint fd, jint optval) { + struct linger solinger; + if (optval < 0) { + solinger.l_onoff = 0; + solinger.l_linger = 0; + } else { + solinger.l_onoff = 1; + solinger.l_linger = optval; + } + netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_LINGER, &solinger, sizeof(solinger)); +} + +static void netty_unix_socket_setTrafficClass(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6, jint optval) { + if (ipv6 == JNI_TRUE) { + // This call will put an exception on the stack to be processed once the JNI calls completes if + // setsockopt failed and return a negative value. + int rc = netty_unix_socket_setOption(env, fd, IPPROTO_IPV6, IPV6_TCLASS, &optval, sizeof(optval)); + + if (rc >= 0) { +/* Linux allows both ipv4 and ipv6 families to be set */ +#ifdef __linux__ + // Previous call successful now try to set also for ipv4 + if (netty_unix_socket_setOption0(fd, IPPROTO_IP, IP_TOS, &optval, sizeof(optval)) == -1) { + if (errno != ENOPROTOOPT) { + // throw exception + netty_unix_socket_setOptionHandleError(env, errno); + } + } +#endif + } + } else { + netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_TOS, &optval, sizeof(optval)); + } +} + +static jint netty_unix_socket_isKeepAlive(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_unix_socket_isTcpNoDelay(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_unix_socket_getReceiveBufferSize(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_RCVBUF, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_unix_socket_getSendBufferSize(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_SNDBUF, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_unix_socket_getSoLinger(JNIEnv* env, jclass clazz, jint fd) { + struct linger optval; + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_LINGER, &optval, sizeof(optval)) == -1) { + return -1; + } + if (optval.l_onoff == 0) { + return -1; + } else { + return optval.l_linger; + } +} + +static jint netty_unix_socket_getTrafficClass(JNIEnv* env, jclass clazz, jint fd, jboolean ipv6) { + int optval; + if (ipv6 == JNI_TRUE) { + if (netty_unix_socket_getOption0(fd, IPPROTO_IPV6, IPV6_TCLASS, &optval, sizeof(optval)) == -1) { + if (errno == ENOPROTOOPT) { + if (netty_unix_socket_getOption(env, fd, IPPROTO_IP, IP_TOS, &optval, sizeof(optval)) == -1) { + return -1; + } + } else { + netty_unix_socket_getOptionHandleError(env, errno); + return -1; + } + } + } else { + if (netty_unix_socket_getOption(env, fd, IPPROTO_IP, IP_TOS, &optval, sizeof(optval)) == -1) { + return -1; + } + } + return optval; +} + +static jint netty_unix_socket_getSoError(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_ERROR, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_unix_socket_isReuseAddress(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_unix_socket_isReusePort(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_unix_socket_isBroadcast(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static void netty_unix_socket_setIntOpt(JNIEnv* env, jclass clazz, jint fd, jint level, jint optname, jint optval) { + netty_unix_socket_setOption(env, fd, level, optname, &optval, sizeof(optval)); +} + +static void netty_unix_socket_setRawOptArray(JNIEnv* env, jclass clazz, jint fd, jint level, jint optname, jbyteArray inArray, jint offset, jint len) { + jbyte* optval = (*env)->GetByteArrayElements(env, inArray, 0); + netty_unix_socket_setOption(env, fd, level, optname, optval + offset, len); + (*env)->ReleaseByteArrayElements(env, inArray, optval, 0); +} + +static void netty_unix_socket_setRawOptAddress(JNIEnv* env, jclass clazz, jint fd, jint level, jint optname, jlong inAddress, jint len) { + netty_unix_socket_setOption(env, fd, level, optname, (void *) inAddress, len); +} + +static jint netty_unix_socket_getIntOpt(JNIEnv* env, jclass clazz, jint fd, jint level, jint optname) { + jint optval; + if (netty_unix_socket_getOption(env, fd, level, optname, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static void netty_unix_socket_getRawOptArray(JNIEnv* env, jclass clazz, jint fd, jint level, jint optname, jbyteArray outArray, jint offset, jint len) { + jbyte* optval = (*env)->GetByteArrayElements(env, outArray, 0); + netty_unix_socket_getOption(env, fd, level, optname, optval + offset, len); + (*env)->ReleaseByteArrayElements(env, outArray, optval, 0); +} + +static void netty_unix_socket_getRawOptAddress(JNIEnv* env, jclass clazz, jint fd, jint level, jint optname, jlong outAddress, jint len) { + netty_unix_socket_getOption(env, fd, level, optname, (void *) outAddress, len); +} + +static jint netty_unix_socket_msgFastopen(JNIEnv* env, jclass clazz) { + return MSG_FASTOPEN; +} + +// JNI Registered Methods End + +// JNI Method Registration Table Begin +static const JNINativeMethod fixed_method_table[] = { + { "shutdown", "(IZZ)I", (void *) netty_unix_socket_shutdown }, + { "bind", "(IZ[BII)I", (void *) netty_unix_socket_bind }, + { "listen", "(II)I", (void *) netty_unix_socket_listen }, + { "connect", "(IZ[BII)I", (void *) netty_unix_socket_connect }, + { "finishConnect", "(I)I", (void *) netty_unix_socket_finishConnect }, + { "disconnect", "(IZ)I", (void *) netty_unix_socket_disconnect}, + { "accept", "(I[B)I", (void *) netty_unix_socket_accept }, + { "remoteAddress", "(I)[B", (void *) netty_unix_socket_remoteAddress }, + { "localAddress", "(I)[B", (void *) netty_unix_socket_localAddress }, + { "remoteDomainSocketAddress", "(I)[B", (void *) netty_unix_socket_remoteDomainSocketAddress }, + { "localDomainSocketAddress", "(I)[B", (void *) netty_unix_socket_localDomainSocketAddress }, + { "newSocketDgramFd", "(Z)I", (void *) netty_unix_socket_newSocketDgramFd }, + { "newSocketStreamFd", "(Z)I", (void *) netty_unix_socket_newSocketStreamFd }, + { "newSocketDomainFd", "()I", (void *) netty_unix_socket_newSocketDomainFd }, + { "newSocketDomainDgramFd", "()I", (void *) netty_unix_socket_newSocketDomainDgramFd }, + { "sendTo", "(IZLjava/nio/ByteBuffer;II[BIII)I", (void *) netty_unix_socket_sendTo }, + { "sendToAddress", "(IZJII[BIII)I", (void *) netty_unix_socket_sendToAddress }, + { "sendToAddresses", "(IZJI[BIII)I", (void *) netty_unix_socket_sendToAddresses }, + { "sendToDomainSocket", "(ILjava/nio/ByteBuffer;II[B)I", (void *) netty_unix_socket_sendToDomainSocket }, + { "sendToAddressDomainSocket", "(IJII[B)I", (void *) netty_unix_socket_sendToAddressDomainSocket }, + { "sendToAddressesDomainSocket", "(IJI[B)I", (void *) netty_unix_socket_sendToAddressesDomainSocket }, + // "recvFrom" has a dynamic signature + // "recvFromAddress" has a dynamic signature + // "recvFromDomainSocket" has a dynamic signature + // "recvFromAddressDomainSocket" has a dynamic signature + { "send", "(ILjava/nio/ByteBuffer;II)I", (void *) netty_unix_socket_send }, + { "sendAddress", "(IJII)I", (void *) netty_unix_socket_sendAddress }, + { "recv", "(ILjava/nio/ByteBuffer;II)I", (void *) netty_unix_socket_recv }, + { "recvAddress", "(IJII)I", (void *) netty_unix_socket_recvAddress }, + { "recvFd", "(I)I", (void *) netty_unix_socket_recvFd }, + { "sendFd", "(II)I", (void *) netty_unix_socket_sendFd }, + { "bindDomainSocket", "(I[B)I", (void *) netty_unix_socket_bindDomainSocket }, + { "connectDomainSocket", "(I[B)I", (void *) netty_unix_socket_connectDomainSocket }, + { "setTcpNoDelay", "(II)V", (void *) netty_unix_socket_setTcpNoDelay }, + { "setReusePort", "(II)V", (void *) netty_unix_socket_setReusePort }, + { "setBroadcast", "(II)V", (void *) netty_unix_socket_setBroadcast }, + { "setReuseAddress", "(II)V", (void *) netty_unix_socket_setReuseAddress }, + { "setReceiveBufferSize", "(II)V", (void *) netty_unix_socket_setReceiveBufferSize }, + { "setSendBufferSize", "(II)V", (void *) netty_unix_socket_setSendBufferSize }, + { "setKeepAlive", "(II)V", (void *) netty_unix_socket_setKeepAlive }, + { "setSoLinger", "(II)V", (void *) netty_unix_socket_setSoLinger }, + { "setTrafficClass", "(IZI)V", (void *) netty_unix_socket_setTrafficClass }, + { "isKeepAlive", "(I)I", (void *) netty_unix_socket_isKeepAlive }, + { "isTcpNoDelay", "(I)I", (void *) netty_unix_socket_isTcpNoDelay }, + { "isBroadcast", "(I)I", (void *) netty_unix_socket_isBroadcast }, + { "isReuseAddress", "(I)I", (void *) netty_unix_socket_isReuseAddress }, + { "isReusePort", "(I)I", (void *) netty_unix_socket_isReusePort }, + { "getReceiveBufferSize", "(I)I", (void *) netty_unix_socket_getReceiveBufferSize }, + { "getSendBufferSize", "(I)I", (void *) netty_unix_socket_getSendBufferSize }, + { "getSoLinger", "(I)I", (void *) netty_unix_socket_getSoLinger }, + { "getTrafficClass", "(IZ)I", (void *) netty_unix_socket_getTrafficClass }, + { "getSoError", "(I)I", (void *) netty_unix_socket_getSoError }, + { "isIPv6Preferred0", "(Z)Z", (void *) netty_unix_socket_isIPv6Preferred0 }, + { "isIPv6", "(I)Z", (void *) netty_unix_socket_isIPv6 }, + { "msgFastopen", "()I", (void *) netty_unix_socket_msgFastopen }, + { "setIntOpt", "(IIII)V", (void *) netty_unix_socket_setIntOpt }, + { "setRawOptArray", "(III[BII)V", (void *) netty_unix_socket_setRawOptArray }, + { "setRawOptAddress", "(IIIJI)V", (void *) netty_unix_socket_setRawOptAddress }, + { "getIntOpt", "(III)I", (void *) netty_unix_socket_getIntOpt }, + { "getRawOptArray", "(III[BII)V", (void *) netty_unix_socket_getRawOptArray }, + { "getRawOptAddress", "(IIIJI)V", (void *) netty_unix_socket_getRawOptAddress } +}; +static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]); + +static jint dynamicMethodsTableSize() { + // 4 is for the dynamic method signatures. + return fixed_method_table_size + 4; +} + +static JNINativeMethod* createDynamicMethodsTable(const char* packagePrefix) { + char* dynamicTypeName = NULL; + size_t size = sizeof(JNINativeMethod) * dynamicMethodsTableSize(); + JNINativeMethod* dynamicMethods = malloc(size); + if (dynamicMethods == NULL) { + return NULL; + } + memset(dynamicMethods, 0, size); + memcpy(dynamicMethods, fixed_method_table, sizeof(fixed_method_table)); + + JNINativeMethod* dynamicMethod = &dynamicMethods[fixed_method_table_size]; + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/unix/DatagramSocketAddress;", dynamicTypeName, error); + NETTY_JNI_UTIL_PREPEND("(ILjava/nio/ByteBuffer;II)L", dynamicTypeName, dynamicMethod->signature, error); + dynamicMethod->name = "recvFrom"; + dynamicMethod->fnPtr = (void *) netty_unix_socket_recvFrom; + netty_jni_util_free_dynamic_name(&dynamicTypeName); + + ++dynamicMethod; + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/unix/DatagramSocketAddress;", dynamicTypeName, error); + NETTY_JNI_UTIL_PREPEND("(IJII)L", dynamicTypeName, dynamicMethod->signature, error); + dynamicMethod->name = "recvFromAddress"; + dynamicMethod->fnPtr = (void *) netty_unix_socket_recvFromAddress; + netty_jni_util_free_dynamic_name(&dynamicTypeName); + + ++dynamicMethod; + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/unix/DomainDatagramSocketAddress;", dynamicTypeName, error); + NETTY_JNI_UTIL_PREPEND("(ILjava/nio/ByteBuffer;II)L", dynamicTypeName, dynamicMethod->signature, error); + dynamicMethod->name = "recvFromDomainSocket"; + dynamicMethod->fnPtr = (void *) netty_unix_socket_recvFromDomainSocket; + netty_jni_util_free_dynamic_name(&dynamicTypeName); + + ++dynamicMethod; + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/unix/DomainDatagramSocketAddress;", dynamicTypeName, error); + NETTY_JNI_UTIL_PREPEND("(IJII)L", dynamicTypeName, dynamicMethod->signature, error); + dynamicMethod->name = "recvFromAddressDomainSocket"; + dynamicMethod->fnPtr = (void *) netty_unix_socket_recvFromAddressDomainSocket; + netty_jni_util_free_dynamic_name(&dynamicTypeName); + + return dynamicMethods; +error: + free(dynamicTypeName); + netty_jni_util_free_dynamic_methods_table(dynamicMethods, fixed_method_table_size, dynamicMethodsTableSize()); + return NULL; +} + +// JNI Method Registration Table End + +// IMPORTANT: If you add any NETTY_JNI_UTIL_LOAD_CLASS or NETTY_JNI_UTIL_FIND_CLASS calls you also need to update +// Unix to reflect that. +jint netty_unix_socket_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { + int ret = JNI_ERR; + char* nettyClassName = NULL; + void* mem = NULL; + jclass datagramSocketAddressClass = NULL; + jclass domainDatagramSocketAddressClass = NULL; + + JNINativeMethod* dynamicMethods = createDynamicMethodsTable(packagePrefix); + if (dynamicMethods == NULL) { + goto done; + } + if (netty_jni_util_register_natives(env, + packagePrefix, + SOCKET_CLASSNAME, + dynamicMethods, + dynamicMethodsTableSize()) != 0) { + goto done; + } + + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/unix/DatagramSocketAddress", nettyClassName, done); + + NETTY_JNI_UTIL_LOAD_CLASS_WEAK(env, datagramSocketAddressClassWeak, nettyClassName, done); + NETTY_JNI_UTIL_NEW_LOCAL_FROM_WEAK(env, datagramSocketAddressClass, datagramSocketAddressClassWeak, done); + + // Respect shading... + char parameters[1024] = {0}; + snprintf(parameters, sizeof(parameters), "([BIIIL%s;)V", nettyClassName); + netty_jni_util_free_dynamic_name(&nettyClassName); + NETTY_JNI_UTIL_GET_METHOD(env, datagramSocketAddressClass, datagramSocketAddrMethodId, "", parameters, done); + + NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/channel/unix/DomainDatagramSocketAddress", nettyClassName, done); + + NETTY_JNI_UTIL_LOAD_CLASS_WEAK(env, domainDatagramSocketAddressClassWeak, nettyClassName, done); + NETTY_JNI_UTIL_NEW_LOCAL_FROM_WEAK(env, domainDatagramSocketAddressClass, domainDatagramSocketAddressClassWeak, done); + + char parameters1[1024] = {0}; + snprintf(parameters1, sizeof(parameters1), "([BIL%s;)V", nettyClassName); + netty_jni_util_free_dynamic_name(&nettyClassName); + NETTY_JNI_UTIL_GET_METHOD(env, domainDatagramSocketAddressClass, domainDatagramSocketAddrMethodId, "", parameters1, done); + + NETTY_JNI_UTIL_LOAD_CLASS(env, inetSocketAddressClass, "java/net/InetSocketAddress", done); + NETTY_JNI_UTIL_GET_METHOD(env, inetSocketAddressClass, inetSocketAddrMethodId, "", "(Ljava/lang/String;I)V", done); + + if ((mem = malloc(1)) == NULL) { + goto done; + } + + jobject directBuffer = (*env)->NewDirectByteBuffer(env, mem, 1); + if (directBuffer == NULL) { + goto done; + } + if ((*env)->GetDirectBufferAddress(env, directBuffer) == NULL) { + goto done; + } + + ret = NETTY_JNI_UTIL_JNI_VERSION; +done: + netty_jni_util_free_dynamic_methods_table(dynamicMethods, fixed_method_table_size, dynamicMethodsTableSize()); + free(nettyClassName); + free(mem); + + NETTY_JNI_UTIL_DELETE_LOCAL(env, datagramSocketAddressClass); + NETTY_JNI_UTIL_DELETE_LOCAL(env, domainDatagramSocketAddressClass); + + return ret; +} + +void netty_unix_socket_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix) { + NETTY_JNI_UTIL_UNLOAD_CLASS_WEAK(env, datagramSocketAddressClassWeak); + NETTY_JNI_UTIL_UNLOAD_CLASS_WEAK(env, domainDatagramSocketAddressClassWeak); + NETTY_JNI_UTIL_UNLOAD_CLASS(env, inetSocketAddressClass); + + netty_jni_util_unregister_natives(env, packagePrefix, SOCKET_CLASSNAME); +} diff --git a/netty-channel-unix-native/src/main/c/netty_unix_util.c b/netty-channel-unix-native/src/main/c/netty_unix_util.c new file mode 100644 index 0000000..b9ebbf3 --- /dev/null +++ b/netty-channel-unix-native/src/main/c/netty_unix_util.c @@ -0,0 +1,112 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include "netty_unix_util.h" + +static const uint64_t NETTY_BILLION = 1000000000L; + +#ifdef NETTY_USE_MACH_INSTEAD_OF_CLOCK + +#include +#include + +#endif /* NETTY_USE_MACH_INSTEAD_OF_CLOCK */ + +// util methods +uint64_t netty_unix_util_timespec_elapsed_ns(const struct timespec* begin, const struct timespec* end) { + return NETTY_BILLION * (end->tv_sec - begin->tv_sec) + (end->tv_nsec - begin->tv_nsec); +} + +jboolean netty_unix_util_timespec_subtract_ns(struct timespec* ts, uint64_t nanos) { + const uint64_t seconds = nanos / NETTY_BILLION; + nanos -= seconds * NETTY_BILLION; + // If there are too many nanos we steal from seconds to avoid underflow on nanos. This way we + // only have to worry about underflow on tv_sec. + if (nanos > ts->tv_nsec) { + --(ts->tv_sec); + ts->tv_nsec += NETTY_BILLION; + } + const jboolean underflow = ts->tv_sec < seconds; + ts->tv_sec -= seconds; + ts->tv_nsec -= nanos; + return underflow; +} + +int netty_unix_util_clock_gettime(clockid_t clockId, struct timespec* tp) { +#ifdef NETTY_USE_MACH_INSTEAD_OF_CLOCK + uint64_t timeNs; + switch (clockId) { + case CLOCK_MONOTONIC_COARSE: + timeNs = mach_approximate_time(); + break; + case CLOCK_MONOTONIC: + timeNs = mach_absolute_time(); + break; + default: + errno = EINVAL; + return -1; + } + // NOTE: this could overflow if time_t is backed by a 32 bit number. + tp->tv_sec = timeNs / NETTY_BILLION; + tp->tv_nsec = timeNs - tp->tv_sec * NETTY_BILLION; // avoid using modulo if not necessary + return 0; +#else + return clock_gettime(clockId, tp); +#endif /* NETTY_USE_MACH_INSTEAD_OF_CLOCK */ +} + +jboolean netty_unix_util_initialize_wait_clock(clockid_t* clockId) { + struct timespec ts; + // First try to get a monotonic clock, as we effectively measure execution time and don't want the underlying clock + // moving unexpectedly/abruptly. +#ifdef CLOCK_MONOTONIC_COARSE + *clockId = CLOCK_MONOTONIC_COARSE; + if (netty_unix_util_clock_gettime(*clockId, &ts) == 0) { + return JNI_TRUE; + } +#endif +#ifdef CLOCK_MONOTONIC_RAW + *clockId = CLOCK_MONOTONIC_RAW; + if (netty_unix_util_clock_gettime(*clockId, &ts) == 0) { + return JNI_TRUE; + } +#endif +#ifdef CLOCK_MONOTONIC + *clockId = CLOCK_MONOTONIC; + if (netty_unix_util_clock_gettime(*clockId, &ts) == 0) { + return JNI_TRUE; + } +#endif + + // Fallback to realtime ... in this case we are subject to clock movements on the system. +#ifdef CLOCK_REALTIME_COARSE + *clockId = CLOCK_REALTIME_COARSE; + if (netty_unix_util_clock_gettime(*clockId, &ts) == 0) { + return JNI_TRUE; + } +#endif +#ifdef CLOCK_REALTIME + *clockId = CLOCK_REALTIME; + if (netty_unix_util_clock_gettime(*clockId, &ts) == 0) { + return JNI_TRUE; + } +#endif + + return JNI_FALSE; +} diff --git a/netty-channel-unix-native/src/main/headers/jni.h b/netty-channel-unix-native/src/main/headers/jni.h new file mode 100644 index 0000000..c85da1b --- /dev/null +++ b/netty-channel-unix-native/src/main/headers/jni.h @@ -0,0 +1,2001 @@ +/* + * Copyright (c) 1996, 2023, 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. + */ + +/* + * We used part of Netscape's Java Runtime Interface (JRI) as the starting + * point of our design and implementation. + */ + +/****************************************************************************** + * Java Runtime Interface + * Copyright (c) 1996 Netscape Communications Corporation. All rights reserved. + *****************************************************************************/ + +#ifndef _JAVASOFT_JNI_H_ +#define _JAVASOFT_JNI_H_ + +#include +#include + +/* jni_md.h contains the machine-dependent typedefs for jbyte, jint + and jlong */ + +#include "jni_md.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * JNI Types + */ + +#ifndef JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H + +typedef unsigned char jboolean; +typedef unsigned short jchar; +typedef short jshort; +typedef float jfloat; +typedef double jdouble; + +typedef jint jsize; + +#ifdef __cplusplus + +class _jobject {}; +class _jclass : public _jobject {}; +class _jthrowable : public _jobject {}; +class _jstring : public _jobject {}; +class _jarray : public _jobject {}; +class _jbooleanArray : public _jarray {}; +class _jbyteArray : public _jarray {}; +class _jcharArray : public _jarray {}; +class _jshortArray : public _jarray {}; +class _jintArray : public _jarray {}; +class _jlongArray : public _jarray {}; +class _jfloatArray : public _jarray {}; +class _jdoubleArray : public _jarray {}; +class _jobjectArray : public _jarray {}; + +typedef _jobject *jobject; +typedef _jclass *jclass; +typedef _jthrowable *jthrowable; +typedef _jstring *jstring; +typedef _jarray *jarray; +typedef _jbooleanArray *jbooleanArray; +typedef _jbyteArray *jbyteArray; +typedef _jcharArray *jcharArray; +typedef _jshortArray *jshortArray; +typedef _jintArray *jintArray; +typedef _jlongArray *jlongArray; +typedef _jfloatArray *jfloatArray; +typedef _jdoubleArray *jdoubleArray; +typedef _jobjectArray *jobjectArray; + +#else + +struct _jobject; + +typedef struct _jobject *jobject; +typedef jobject jclass; +typedef jobject jthrowable; +typedef jobject jstring; +typedef jobject jarray; +typedef jarray jbooleanArray; +typedef jarray jbyteArray; +typedef jarray jcharArray; +typedef jarray jshortArray; +typedef jarray jintArray; +typedef jarray jlongArray; +typedef jarray jfloatArray; +typedef jarray jdoubleArray; +typedef jarray jobjectArray; + +#endif + +typedef jobject jweak; + +typedef union jvalue { + jboolean z; + jbyte b; + jchar c; + jshort s; + jint i; + jlong j; + jfloat f; + jdouble d; + jobject l; +} jvalue; + +struct _jfieldID; +typedef struct _jfieldID *jfieldID; + +struct _jmethodID; +typedef struct _jmethodID *jmethodID; + +/* Return values from jobjectRefType */ +typedef enum _jobjectType { + JNIInvalidRefType = 0, + JNILocalRefType = 1, + JNIGlobalRefType = 2, + JNIWeakGlobalRefType = 3 +} jobjectRefType; + + +#endif /* JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H */ + +/* + * jboolean constants + */ + +#define JNI_FALSE 0 +#define JNI_TRUE 1 + +/* + * possible return values for JNI functions. + */ + +#define JNI_OK 0 /* success */ +#define JNI_ERR (-1) /* unknown error */ +#define JNI_EDETACHED (-2) /* thread detached from the VM */ +#define JNI_EVERSION (-3) /* JNI version error */ +#define JNI_ENOMEM (-4) /* not enough memory */ +#define JNI_EEXIST (-5) /* VM already created */ +#define JNI_EINVAL (-6) /* invalid arguments */ + +/* + * used in ReleaseScalarArrayElements + */ + +#define JNI_COMMIT 1 +#define JNI_ABORT 2 + +/* + * used in RegisterNatives to describe native method name, signature, + * and function pointer. + */ + +typedef struct { + char *name; + char *signature; + void *fnPtr; +} JNINativeMethod; + +/* + * JNI Native Method Interface. + */ + +struct JNINativeInterface_; + +struct JNIEnv_; + +#ifdef __cplusplus +typedef JNIEnv_ JNIEnv; +#else +typedef const struct JNINativeInterface_ *JNIEnv; +#endif + +/* + * JNI Invocation Interface. + */ + +struct JNIInvokeInterface_; + +struct JavaVM_; + +#ifdef __cplusplus +typedef JavaVM_ JavaVM; +#else +typedef const struct JNIInvokeInterface_ *JavaVM; +#endif + +struct JNINativeInterface_ { + void *reserved0; + void *reserved1; + void *reserved2; + + void *reserved3; + jint (JNICALL *GetVersion)(JNIEnv *env); + + jclass (JNICALL *DefineClass) + (JNIEnv *env, const char *name, jobject loader, const jbyte *buf, + jsize len); + jclass (JNICALL *FindClass) + (JNIEnv *env, const char *name); + + jmethodID (JNICALL *FromReflectedMethod) + (JNIEnv *env, jobject method); + jfieldID (JNICALL *FromReflectedField) + (JNIEnv *env, jobject field); + + jobject (JNICALL *ToReflectedMethod) + (JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic); + + jclass (JNICALL *GetSuperclass) + (JNIEnv *env, jclass sub); + jboolean (JNICALL *IsAssignableFrom) + (JNIEnv *env, jclass sub, jclass sup); + + jobject (JNICALL *ToReflectedField) + (JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic); + + jint (JNICALL *Throw) + (JNIEnv *env, jthrowable obj); + jint (JNICALL *ThrowNew) + (JNIEnv *env, jclass clazz, const char *msg); + jthrowable (JNICALL *ExceptionOccurred) + (JNIEnv *env); + void (JNICALL *ExceptionDescribe) + (JNIEnv *env); + void (JNICALL *ExceptionClear) + (JNIEnv *env); + void (JNICALL *FatalError) + (JNIEnv *env, const char *msg); + + jint (JNICALL *PushLocalFrame) + (JNIEnv *env, jint capacity); + jobject (JNICALL *PopLocalFrame) + (JNIEnv *env, jobject result); + + jobject (JNICALL *NewGlobalRef) + (JNIEnv *env, jobject lobj); + void (JNICALL *DeleteGlobalRef) + (JNIEnv *env, jobject gref); + void (JNICALL *DeleteLocalRef) + (JNIEnv *env, jobject obj); + jboolean (JNICALL *IsSameObject) + (JNIEnv *env, jobject obj1, jobject obj2); + jobject (JNICALL *NewLocalRef) + (JNIEnv *env, jobject ref); + jint (JNICALL *EnsureLocalCapacity) + (JNIEnv *env, jint capacity); + + jobject (JNICALL *AllocObject) + (JNIEnv *env, jclass clazz); + jobject (JNICALL *NewObject) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jobject (JNICALL *NewObjectV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jobject (JNICALL *NewObjectA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jclass (JNICALL *GetObjectClass) + (JNIEnv *env, jobject obj); + jboolean (JNICALL *IsInstanceOf) + (JNIEnv *env, jobject obj, jclass clazz); + + jmethodID (JNICALL *GetMethodID) + (JNIEnv *env, jclass clazz, const char *name, const char *sig); + + jobject (JNICALL *CallObjectMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jobject (JNICALL *CallObjectMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jobject (JNICALL *CallObjectMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args); + + jboolean (JNICALL *CallBooleanMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jboolean (JNICALL *CallBooleanMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jboolean (JNICALL *CallBooleanMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args); + + jbyte (JNICALL *CallByteMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jbyte (JNICALL *CallByteMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jbyte (JNICALL *CallByteMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jchar (JNICALL *CallCharMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jchar (JNICALL *CallCharMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jchar (JNICALL *CallCharMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jshort (JNICALL *CallShortMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jshort (JNICALL *CallShortMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jshort (JNICALL *CallShortMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jint (JNICALL *CallIntMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jint (JNICALL *CallIntMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jint (JNICALL *CallIntMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jlong (JNICALL *CallLongMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jlong (JNICALL *CallLongMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jlong (JNICALL *CallLongMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jfloat (JNICALL *CallFloatMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jfloat (JNICALL *CallFloatMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jfloat (JNICALL *CallFloatMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jdouble (JNICALL *CallDoubleMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jdouble (JNICALL *CallDoubleMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jdouble (JNICALL *CallDoubleMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + void (JNICALL *CallVoidMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + void (JNICALL *CallVoidMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + void (JNICALL *CallVoidMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args); + + jobject (JNICALL *CallNonvirtualObjectMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jobject (JNICALL *CallNonvirtualObjectMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jobject (JNICALL *CallNonvirtualObjectMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue * args); + + jboolean (JNICALL *CallNonvirtualBooleanMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jboolean (JNICALL *CallNonvirtualBooleanMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jboolean (JNICALL *CallNonvirtualBooleanMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue * args); + + jbyte (JNICALL *CallNonvirtualByteMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jbyte (JNICALL *CallNonvirtualByteMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jbyte (JNICALL *CallNonvirtualByteMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + jchar (JNICALL *CallNonvirtualCharMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jchar (JNICALL *CallNonvirtualCharMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jchar (JNICALL *CallNonvirtualCharMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + jshort (JNICALL *CallNonvirtualShortMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jshort (JNICALL *CallNonvirtualShortMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jshort (JNICALL *CallNonvirtualShortMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + jint (JNICALL *CallNonvirtualIntMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jint (JNICALL *CallNonvirtualIntMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jint (JNICALL *CallNonvirtualIntMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + jlong (JNICALL *CallNonvirtualLongMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jlong (JNICALL *CallNonvirtualLongMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jlong (JNICALL *CallNonvirtualLongMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + jfloat (JNICALL *CallNonvirtualFloatMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jfloat (JNICALL *CallNonvirtualFloatMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jfloat (JNICALL *CallNonvirtualFloatMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + jdouble (JNICALL *CallNonvirtualDoubleMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jdouble (JNICALL *CallNonvirtualDoubleMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jdouble (JNICALL *CallNonvirtualDoubleMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + void (JNICALL *CallNonvirtualVoidMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + void (JNICALL *CallNonvirtualVoidMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + void (JNICALL *CallNonvirtualVoidMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue * args); + + jfieldID (JNICALL *GetFieldID) + (JNIEnv *env, jclass clazz, const char *name, const char *sig); + + jobject (JNICALL *GetObjectField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jboolean (JNICALL *GetBooleanField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jbyte (JNICALL *GetByteField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jchar (JNICALL *GetCharField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jshort (JNICALL *GetShortField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jint (JNICALL *GetIntField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jlong (JNICALL *GetLongField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jfloat (JNICALL *GetFloatField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jdouble (JNICALL *GetDoubleField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + + void (JNICALL *SetObjectField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jobject val); + void (JNICALL *SetBooleanField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jboolean val); + void (JNICALL *SetByteField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jbyte val); + void (JNICALL *SetCharField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jchar val); + void (JNICALL *SetShortField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jshort val); + void (JNICALL *SetIntField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jint val); + void (JNICALL *SetLongField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jlong val); + void (JNICALL *SetFloatField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jfloat val); + void (JNICALL *SetDoubleField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jdouble val); + + jmethodID (JNICALL *GetStaticMethodID) + (JNIEnv *env, jclass clazz, const char *name, const char *sig); + + jobject (JNICALL *CallStaticObjectMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jobject (JNICALL *CallStaticObjectMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jobject (JNICALL *CallStaticObjectMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jboolean (JNICALL *CallStaticBooleanMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jboolean (JNICALL *CallStaticBooleanMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jboolean (JNICALL *CallStaticBooleanMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jbyte (JNICALL *CallStaticByteMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jbyte (JNICALL *CallStaticByteMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jbyte (JNICALL *CallStaticByteMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jchar (JNICALL *CallStaticCharMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jchar (JNICALL *CallStaticCharMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jchar (JNICALL *CallStaticCharMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jshort (JNICALL *CallStaticShortMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jshort (JNICALL *CallStaticShortMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jshort (JNICALL *CallStaticShortMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jint (JNICALL *CallStaticIntMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jint (JNICALL *CallStaticIntMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jint (JNICALL *CallStaticIntMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jlong (JNICALL *CallStaticLongMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jlong (JNICALL *CallStaticLongMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jlong (JNICALL *CallStaticLongMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jfloat (JNICALL *CallStaticFloatMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jfloat (JNICALL *CallStaticFloatMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jfloat (JNICALL *CallStaticFloatMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jdouble (JNICALL *CallStaticDoubleMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jdouble (JNICALL *CallStaticDoubleMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jdouble (JNICALL *CallStaticDoubleMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + void (JNICALL *CallStaticVoidMethod) + (JNIEnv *env, jclass cls, jmethodID methodID, ...); + void (JNICALL *CallStaticVoidMethodV) + (JNIEnv *env, jclass cls, jmethodID methodID, va_list args); + void (JNICALL *CallStaticVoidMethodA) + (JNIEnv *env, jclass cls, jmethodID methodID, const jvalue * args); + + jfieldID (JNICALL *GetStaticFieldID) + (JNIEnv *env, jclass clazz, const char *name, const char *sig); + jobject (JNICALL *GetStaticObjectField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jboolean (JNICALL *GetStaticBooleanField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jbyte (JNICALL *GetStaticByteField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jchar (JNICALL *GetStaticCharField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jshort (JNICALL *GetStaticShortField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jint (JNICALL *GetStaticIntField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jlong (JNICALL *GetStaticLongField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jfloat (JNICALL *GetStaticFloatField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jdouble (JNICALL *GetStaticDoubleField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + + void (JNICALL *SetStaticObjectField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jobject value); + void (JNICALL *SetStaticBooleanField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jboolean value); + void (JNICALL *SetStaticByteField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jbyte value); + void (JNICALL *SetStaticCharField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jchar value); + void (JNICALL *SetStaticShortField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jshort value); + void (JNICALL *SetStaticIntField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jint value); + void (JNICALL *SetStaticLongField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jlong value); + void (JNICALL *SetStaticFloatField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jfloat value); + void (JNICALL *SetStaticDoubleField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jdouble value); + + jstring (JNICALL *NewString) + (JNIEnv *env, const jchar *unicode, jsize len); + jsize (JNICALL *GetStringLength) + (JNIEnv *env, jstring str); + const jchar *(JNICALL *GetStringChars) + (JNIEnv *env, jstring str, jboolean *isCopy); + void (JNICALL *ReleaseStringChars) + (JNIEnv *env, jstring str, const jchar *chars); + + jstring (JNICALL *NewStringUTF) + (JNIEnv *env, const char *utf); + jsize (JNICALL *GetStringUTFLength) + (JNIEnv *env, jstring str); + const char* (JNICALL *GetStringUTFChars) + (JNIEnv *env, jstring str, jboolean *isCopy); + void (JNICALL *ReleaseStringUTFChars) + (JNIEnv *env, jstring str, const char* chars); + + + jsize (JNICALL *GetArrayLength) + (JNIEnv *env, jarray array); + + jobjectArray (JNICALL *NewObjectArray) + (JNIEnv *env, jsize len, jclass clazz, jobject init); + jobject (JNICALL *GetObjectArrayElement) + (JNIEnv *env, jobjectArray array, jsize index); + void (JNICALL *SetObjectArrayElement) + (JNIEnv *env, jobjectArray array, jsize index, jobject val); + + jbooleanArray (JNICALL *NewBooleanArray) + (JNIEnv *env, jsize len); + jbyteArray (JNICALL *NewByteArray) + (JNIEnv *env, jsize len); + jcharArray (JNICALL *NewCharArray) + (JNIEnv *env, jsize len); + jshortArray (JNICALL *NewShortArray) + (JNIEnv *env, jsize len); + jintArray (JNICALL *NewIntArray) + (JNIEnv *env, jsize len); + jlongArray (JNICALL *NewLongArray) + (JNIEnv *env, jsize len); + jfloatArray (JNICALL *NewFloatArray) + (JNIEnv *env, jsize len); + jdoubleArray (JNICALL *NewDoubleArray) + (JNIEnv *env, jsize len); + + jboolean * (JNICALL *GetBooleanArrayElements) + (JNIEnv *env, jbooleanArray array, jboolean *isCopy); + jbyte * (JNICALL *GetByteArrayElements) + (JNIEnv *env, jbyteArray array, jboolean *isCopy); + jchar * (JNICALL *GetCharArrayElements) + (JNIEnv *env, jcharArray array, jboolean *isCopy); + jshort * (JNICALL *GetShortArrayElements) + (JNIEnv *env, jshortArray array, jboolean *isCopy); + jint * (JNICALL *GetIntArrayElements) + (JNIEnv *env, jintArray array, jboolean *isCopy); + jlong * (JNICALL *GetLongArrayElements) + (JNIEnv *env, jlongArray array, jboolean *isCopy); + jfloat * (JNICALL *GetFloatArrayElements) + (JNIEnv *env, jfloatArray array, jboolean *isCopy); + jdouble * (JNICALL *GetDoubleArrayElements) + (JNIEnv *env, jdoubleArray array, jboolean *isCopy); + + void (JNICALL *ReleaseBooleanArrayElements) + (JNIEnv *env, jbooleanArray array, jboolean *elems, jint mode); + void (JNICALL *ReleaseByteArrayElements) + (JNIEnv *env, jbyteArray array, jbyte *elems, jint mode); + void (JNICALL *ReleaseCharArrayElements) + (JNIEnv *env, jcharArray array, jchar *elems, jint mode); + void (JNICALL *ReleaseShortArrayElements) + (JNIEnv *env, jshortArray array, jshort *elems, jint mode); + void (JNICALL *ReleaseIntArrayElements) + (JNIEnv *env, jintArray array, jint *elems, jint mode); + void (JNICALL *ReleaseLongArrayElements) + (JNIEnv *env, jlongArray array, jlong *elems, jint mode); + void (JNICALL *ReleaseFloatArrayElements) + (JNIEnv *env, jfloatArray array, jfloat *elems, jint mode); + void (JNICALL *ReleaseDoubleArrayElements) + (JNIEnv *env, jdoubleArray array, jdouble *elems, jint mode); + + void (JNICALL *GetBooleanArrayRegion) + (JNIEnv *env, jbooleanArray array, jsize start, jsize l, jboolean *buf); + void (JNICALL *GetByteArrayRegion) + (JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf); + void (JNICALL *GetCharArrayRegion) + (JNIEnv *env, jcharArray array, jsize start, jsize len, jchar *buf); + void (JNICALL *GetShortArrayRegion) + (JNIEnv *env, jshortArray array, jsize start, jsize len, jshort *buf); + void (JNICALL *GetIntArrayRegion) + (JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf); + void (JNICALL *GetLongArrayRegion) + (JNIEnv *env, jlongArray array, jsize start, jsize len, jlong *buf); + void (JNICALL *GetFloatArrayRegion) + (JNIEnv *env, jfloatArray array, jsize start, jsize len, jfloat *buf); + void (JNICALL *GetDoubleArrayRegion) + (JNIEnv *env, jdoubleArray array, jsize start, jsize len, jdouble *buf); + + void (JNICALL *SetBooleanArrayRegion) + (JNIEnv *env, jbooleanArray array, jsize start, jsize l, const jboolean *buf); + void (JNICALL *SetByteArrayRegion) + (JNIEnv *env, jbyteArray array, jsize start, jsize len, const jbyte *buf); + void (JNICALL *SetCharArrayRegion) + (JNIEnv *env, jcharArray array, jsize start, jsize len, const jchar *buf); + void (JNICALL *SetShortArrayRegion) + (JNIEnv *env, jshortArray array, jsize start, jsize len, const jshort *buf); + void (JNICALL *SetIntArrayRegion) + (JNIEnv *env, jintArray array, jsize start, jsize len, const jint *buf); + void (JNICALL *SetLongArrayRegion) + (JNIEnv *env, jlongArray array, jsize start, jsize len, const jlong *buf); + void (JNICALL *SetFloatArrayRegion) + (JNIEnv *env, jfloatArray array, jsize start, jsize len, const jfloat *buf); + void (JNICALL *SetDoubleArrayRegion) + (JNIEnv *env, jdoubleArray array, jsize start, jsize len, const jdouble *buf); + + jint (JNICALL *RegisterNatives) + (JNIEnv *env, jclass clazz, const JNINativeMethod *methods, + jint nMethods); + jint (JNICALL *UnregisterNatives) + (JNIEnv *env, jclass clazz); + + jint (JNICALL *MonitorEnter) + (JNIEnv *env, jobject obj); + jint (JNICALL *MonitorExit) + (JNIEnv *env, jobject obj); + + jint (JNICALL *GetJavaVM) + (JNIEnv *env, JavaVM **vm); + + void (JNICALL *GetStringRegion) + (JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf); + void (JNICALL *GetStringUTFRegion) + (JNIEnv *env, jstring str, jsize start, jsize len, char *buf); + + void * (JNICALL *GetPrimitiveArrayCritical) + (JNIEnv *env, jarray array, jboolean *isCopy); + void (JNICALL *ReleasePrimitiveArrayCritical) + (JNIEnv *env, jarray array, void *carray, jint mode); + + const jchar * (JNICALL *GetStringCritical) + (JNIEnv *env, jstring string, jboolean *isCopy); + void (JNICALL *ReleaseStringCritical) + (JNIEnv *env, jstring string, const jchar *cstring); + + jweak (JNICALL *NewWeakGlobalRef) + (JNIEnv *env, jobject obj); + void (JNICALL *DeleteWeakGlobalRef) + (JNIEnv *env, jweak ref); + + jboolean (JNICALL *ExceptionCheck) + (JNIEnv *env); + + jobject (JNICALL *NewDirectByteBuffer) + (JNIEnv* env, void* address, jlong capacity); + void* (JNICALL *GetDirectBufferAddress) + (JNIEnv* env, jobject buf); + jlong (JNICALL *GetDirectBufferCapacity) + (JNIEnv* env, jobject buf); + + /* New JNI 1.6 Features */ + + jobjectRefType (JNICALL *GetObjectRefType) + (JNIEnv* env, jobject obj); + + /* Module Features */ + + jobject (JNICALL *GetModule) + (JNIEnv* env, jclass clazz); + + /* Virtual threads */ + + jboolean (JNICALL *IsVirtualThread) + (JNIEnv* env, jobject obj); +}; + +/* + * We use inlined functions for C++ so that programmers can write: + * + * env->FindClass("java/lang/String") + * + * in C++ rather than: + * + * (*env)->FindClass(env, "java/lang/String") + * + * in C. + */ + +struct JNIEnv_ { + const struct JNINativeInterface_ *functions; +#ifdef __cplusplus + + jint GetVersion() { + return functions->GetVersion(this); + } + jclass DefineClass(const char *name, jobject loader, const jbyte *buf, + jsize len) { + return functions->DefineClass(this, name, loader, buf, len); + } + jclass FindClass(const char *name) { + return functions->FindClass(this, name); + } + jmethodID FromReflectedMethod(jobject method) { + return functions->FromReflectedMethod(this,method); + } + jfieldID FromReflectedField(jobject field) { + return functions->FromReflectedField(this,field); + } + + jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) { + return functions->ToReflectedMethod(this, cls, methodID, isStatic); + } + + jclass GetSuperclass(jclass sub) { + return functions->GetSuperclass(this, sub); + } + jboolean IsAssignableFrom(jclass sub, jclass sup) { + return functions->IsAssignableFrom(this, sub, sup); + } + + jobject ToReflectedField(jclass cls, jfieldID fieldID, jboolean isStatic) { + return functions->ToReflectedField(this,cls,fieldID,isStatic); + } + + jint Throw(jthrowable obj) { + return functions->Throw(this, obj); + } + jint ThrowNew(jclass clazz, const char *msg) { + return functions->ThrowNew(this, clazz, msg); + } + jthrowable ExceptionOccurred() { + return functions->ExceptionOccurred(this); + } + void ExceptionDescribe() { + functions->ExceptionDescribe(this); + } + void ExceptionClear() { + functions->ExceptionClear(this); + } + void FatalError(const char *msg) { + functions->FatalError(this, msg); + } + + jint PushLocalFrame(jint capacity) { + return functions->PushLocalFrame(this,capacity); + } + jobject PopLocalFrame(jobject result) { + return functions->PopLocalFrame(this,result); + } + + jobject NewGlobalRef(jobject lobj) { + return functions->NewGlobalRef(this,lobj); + } + void DeleteGlobalRef(jobject gref) { + functions->DeleteGlobalRef(this,gref); + } + void DeleteLocalRef(jobject obj) { + functions->DeleteLocalRef(this, obj); + } + + jboolean IsSameObject(jobject obj1, jobject obj2) { + return functions->IsSameObject(this,obj1,obj2); + } + + jobject NewLocalRef(jobject ref) { + return functions->NewLocalRef(this,ref); + } + jint EnsureLocalCapacity(jint capacity) { + return functions->EnsureLocalCapacity(this,capacity); + } + + jobject AllocObject(jclass clazz) { + return functions->AllocObject(this,clazz); + } + jobject NewObject(jclass clazz, jmethodID methodID, ...) { + va_list args; + jobject result; + va_start(args, methodID); + result = functions->NewObjectV(this,clazz,methodID,args); + va_end(args); + return result; + } + jobject NewObjectV(jclass clazz, jmethodID methodID, + va_list args) { + return functions->NewObjectV(this,clazz,methodID,args); + } + jobject NewObjectA(jclass clazz, jmethodID methodID, + const jvalue *args) { + return functions->NewObjectA(this,clazz,methodID,args); + } + + jclass GetObjectClass(jobject obj) { + return functions->GetObjectClass(this,obj); + } + jboolean IsInstanceOf(jobject obj, jclass clazz) { + return functions->IsInstanceOf(this,obj,clazz); + } + + jmethodID GetMethodID(jclass clazz, const char *name, + const char *sig) { + return functions->GetMethodID(this,clazz,name,sig); + } + + jobject CallObjectMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jobject result; + va_start(args,methodID); + result = functions->CallObjectMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jobject CallObjectMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallObjectMethodV(this,obj,methodID,args); + } + jobject CallObjectMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallObjectMethodA(this,obj,methodID,args); + } + + jboolean CallBooleanMethod(jobject obj, + jmethodID methodID, ...) { + va_list args; + jboolean result; + va_start(args,methodID); + result = functions->CallBooleanMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jboolean CallBooleanMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallBooleanMethodV(this,obj,methodID,args); + } + jboolean CallBooleanMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallBooleanMethodA(this,obj,methodID, args); + } + + jbyte CallByteMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jbyte result; + va_start(args,methodID); + result = functions->CallByteMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jbyte CallByteMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallByteMethodV(this,obj,methodID,args); + } + jbyte CallByteMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallByteMethodA(this,obj,methodID,args); + } + + jchar CallCharMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jchar result; + va_start(args,methodID); + result = functions->CallCharMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jchar CallCharMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallCharMethodV(this,obj,methodID,args); + } + jchar CallCharMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallCharMethodA(this,obj,methodID,args); + } + + jshort CallShortMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jshort result; + va_start(args,methodID); + result = functions->CallShortMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jshort CallShortMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallShortMethodV(this,obj,methodID,args); + } + jshort CallShortMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallShortMethodA(this,obj,methodID,args); + } + + jint CallIntMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jint result; + va_start(args,methodID); + result = functions->CallIntMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jint CallIntMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallIntMethodV(this,obj,methodID,args); + } + jint CallIntMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallIntMethodA(this,obj,methodID,args); + } + + jlong CallLongMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jlong result; + va_start(args,methodID); + result = functions->CallLongMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jlong CallLongMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallLongMethodV(this,obj,methodID,args); + } + jlong CallLongMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallLongMethodA(this,obj,methodID,args); + } + + jfloat CallFloatMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jfloat result; + va_start(args,methodID); + result = functions->CallFloatMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jfloat CallFloatMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallFloatMethodV(this,obj,methodID,args); + } + jfloat CallFloatMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallFloatMethodA(this,obj,methodID,args); + } + + jdouble CallDoubleMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jdouble result; + va_start(args,methodID); + result = functions->CallDoubleMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jdouble CallDoubleMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallDoubleMethodV(this,obj,methodID,args); + } + jdouble CallDoubleMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallDoubleMethodA(this,obj,methodID,args); + } + + void CallVoidMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + va_start(args,methodID); + functions->CallVoidMethodV(this,obj,methodID,args); + va_end(args); + } + void CallVoidMethodV(jobject obj, jmethodID methodID, + va_list args) { + functions->CallVoidMethodV(this,obj,methodID,args); + } + void CallVoidMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + functions->CallVoidMethodA(this,obj,methodID,args); + } + + jobject CallNonvirtualObjectMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jobject result; + va_start(args,methodID); + result = functions->CallNonvirtualObjectMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jobject CallNonvirtualObjectMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualObjectMethodV(this,obj,clazz, + methodID,args); + } + jobject CallNonvirtualObjectMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualObjectMethodA(this,obj,clazz, + methodID,args); + } + + jboolean CallNonvirtualBooleanMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jboolean result; + va_start(args,methodID); + result = functions->CallNonvirtualBooleanMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jboolean CallNonvirtualBooleanMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualBooleanMethodV(this,obj,clazz, + methodID,args); + } + jboolean CallNonvirtualBooleanMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualBooleanMethodA(this,obj,clazz, + methodID, args); + } + + jbyte CallNonvirtualByteMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jbyte result; + va_start(args,methodID); + result = functions->CallNonvirtualByteMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jbyte CallNonvirtualByteMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualByteMethodV(this,obj,clazz, + methodID,args); + } + jbyte CallNonvirtualByteMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualByteMethodA(this,obj,clazz, + methodID,args); + } + + jchar CallNonvirtualCharMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jchar result; + va_start(args,methodID); + result = functions->CallNonvirtualCharMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jchar CallNonvirtualCharMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualCharMethodV(this,obj,clazz, + methodID,args); + } + jchar CallNonvirtualCharMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualCharMethodA(this,obj,clazz, + methodID,args); + } + + jshort CallNonvirtualShortMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jshort result; + va_start(args,methodID); + result = functions->CallNonvirtualShortMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jshort CallNonvirtualShortMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualShortMethodV(this,obj,clazz, + methodID,args); + } + jshort CallNonvirtualShortMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualShortMethodA(this,obj,clazz, + methodID,args); + } + + jint CallNonvirtualIntMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jint result; + va_start(args,methodID); + result = functions->CallNonvirtualIntMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jint CallNonvirtualIntMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualIntMethodV(this,obj,clazz, + methodID,args); + } + jint CallNonvirtualIntMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualIntMethodA(this,obj,clazz, + methodID,args); + } + + jlong CallNonvirtualLongMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jlong result; + va_start(args,methodID); + result = functions->CallNonvirtualLongMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jlong CallNonvirtualLongMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualLongMethodV(this,obj,clazz, + methodID,args); + } + jlong CallNonvirtualLongMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualLongMethodA(this,obj,clazz, + methodID,args); + } + + jfloat CallNonvirtualFloatMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jfloat result; + va_start(args,methodID); + result = functions->CallNonvirtualFloatMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jfloat CallNonvirtualFloatMethodV(jobject obj, jclass clazz, + jmethodID methodID, + va_list args) { + return functions->CallNonvirtualFloatMethodV(this,obj,clazz, + methodID,args); + } + jfloat CallNonvirtualFloatMethodA(jobject obj, jclass clazz, + jmethodID methodID, + const jvalue * args) { + return functions->CallNonvirtualFloatMethodA(this,obj,clazz, + methodID,args); + } + + jdouble CallNonvirtualDoubleMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jdouble result; + va_start(args,methodID); + result = functions->CallNonvirtualDoubleMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jdouble CallNonvirtualDoubleMethodV(jobject obj, jclass clazz, + jmethodID methodID, + va_list args) { + return functions->CallNonvirtualDoubleMethodV(this,obj,clazz, + methodID,args); + } + jdouble CallNonvirtualDoubleMethodA(jobject obj, jclass clazz, + jmethodID methodID, + const jvalue * args) { + return functions->CallNonvirtualDoubleMethodA(this,obj,clazz, + methodID,args); + } + + void CallNonvirtualVoidMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + va_start(args,methodID); + functions->CallNonvirtualVoidMethodV(this,obj,clazz,methodID,args); + va_end(args); + } + void CallNonvirtualVoidMethodV(jobject obj, jclass clazz, + jmethodID methodID, + va_list args) { + functions->CallNonvirtualVoidMethodV(this,obj,clazz,methodID,args); + } + void CallNonvirtualVoidMethodA(jobject obj, jclass clazz, + jmethodID methodID, + const jvalue * args) { + functions->CallNonvirtualVoidMethodA(this,obj,clazz,methodID,args); + } + + jfieldID GetFieldID(jclass clazz, const char *name, + const char *sig) { + return functions->GetFieldID(this,clazz,name,sig); + } + + jobject GetObjectField(jobject obj, jfieldID fieldID) { + return functions->GetObjectField(this,obj,fieldID); + } + jboolean GetBooleanField(jobject obj, jfieldID fieldID) { + return functions->GetBooleanField(this,obj,fieldID); + } + jbyte GetByteField(jobject obj, jfieldID fieldID) { + return functions->GetByteField(this,obj,fieldID); + } + jchar GetCharField(jobject obj, jfieldID fieldID) { + return functions->GetCharField(this,obj,fieldID); + } + jshort GetShortField(jobject obj, jfieldID fieldID) { + return functions->GetShortField(this,obj,fieldID); + } + jint GetIntField(jobject obj, jfieldID fieldID) { + return functions->GetIntField(this,obj,fieldID); + } + jlong GetLongField(jobject obj, jfieldID fieldID) { + return functions->GetLongField(this,obj,fieldID); + } + jfloat GetFloatField(jobject obj, jfieldID fieldID) { + return functions->GetFloatField(this,obj,fieldID); + } + jdouble GetDoubleField(jobject obj, jfieldID fieldID) { + return functions->GetDoubleField(this,obj,fieldID); + } + + void SetObjectField(jobject obj, jfieldID fieldID, jobject val) { + functions->SetObjectField(this,obj,fieldID,val); + } + void SetBooleanField(jobject obj, jfieldID fieldID, + jboolean val) { + functions->SetBooleanField(this,obj,fieldID,val); + } + void SetByteField(jobject obj, jfieldID fieldID, + jbyte val) { + functions->SetByteField(this,obj,fieldID,val); + } + void SetCharField(jobject obj, jfieldID fieldID, + jchar val) { + functions->SetCharField(this,obj,fieldID,val); + } + void SetShortField(jobject obj, jfieldID fieldID, + jshort val) { + functions->SetShortField(this,obj,fieldID,val); + } + void SetIntField(jobject obj, jfieldID fieldID, + jint val) { + functions->SetIntField(this,obj,fieldID,val); + } + void SetLongField(jobject obj, jfieldID fieldID, + jlong val) { + functions->SetLongField(this,obj,fieldID,val); + } + void SetFloatField(jobject obj, jfieldID fieldID, + jfloat val) { + functions->SetFloatField(this,obj,fieldID,val); + } + void SetDoubleField(jobject obj, jfieldID fieldID, + jdouble val) { + functions->SetDoubleField(this,obj,fieldID,val); + } + + jmethodID GetStaticMethodID(jclass clazz, const char *name, + const char *sig) { + return functions->GetStaticMethodID(this,clazz,name,sig); + } + + jobject CallStaticObjectMethod(jclass clazz, jmethodID methodID, + ...) { + va_list args; + jobject result; + va_start(args,methodID); + result = functions->CallStaticObjectMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jobject CallStaticObjectMethodV(jclass clazz, jmethodID methodID, + va_list args) { + return functions->CallStaticObjectMethodV(this,clazz,methodID,args); + } + jobject CallStaticObjectMethodA(jclass clazz, jmethodID methodID, + const jvalue *args) { + return functions->CallStaticObjectMethodA(this,clazz,methodID,args); + } + + jboolean CallStaticBooleanMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jboolean result; + va_start(args,methodID); + result = functions->CallStaticBooleanMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jboolean CallStaticBooleanMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticBooleanMethodV(this,clazz,methodID,args); + } + jboolean CallStaticBooleanMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticBooleanMethodA(this,clazz,methodID,args); + } + + jbyte CallStaticByteMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jbyte result; + va_start(args,methodID); + result = functions->CallStaticByteMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jbyte CallStaticByteMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticByteMethodV(this,clazz,methodID,args); + } + jbyte CallStaticByteMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticByteMethodA(this,clazz,methodID,args); + } + + jchar CallStaticCharMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jchar result; + va_start(args,methodID); + result = functions->CallStaticCharMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jchar CallStaticCharMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticCharMethodV(this,clazz,methodID,args); + } + jchar CallStaticCharMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticCharMethodA(this,clazz,methodID,args); + } + + jshort CallStaticShortMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jshort result; + va_start(args,methodID); + result = functions->CallStaticShortMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jshort CallStaticShortMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticShortMethodV(this,clazz,methodID,args); + } + jshort CallStaticShortMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticShortMethodA(this,clazz,methodID,args); + } + + jint CallStaticIntMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jint result; + va_start(args,methodID); + result = functions->CallStaticIntMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jint CallStaticIntMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticIntMethodV(this,clazz,methodID,args); + } + jint CallStaticIntMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticIntMethodA(this,clazz,methodID,args); + } + + jlong CallStaticLongMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jlong result; + va_start(args,methodID); + result = functions->CallStaticLongMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jlong CallStaticLongMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticLongMethodV(this,clazz,methodID,args); + } + jlong CallStaticLongMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticLongMethodA(this,clazz,methodID,args); + } + + jfloat CallStaticFloatMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jfloat result; + va_start(args,methodID); + result = functions->CallStaticFloatMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jfloat CallStaticFloatMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticFloatMethodV(this,clazz,methodID,args); + } + jfloat CallStaticFloatMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticFloatMethodA(this,clazz,methodID,args); + } + + jdouble CallStaticDoubleMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jdouble result; + va_start(args,methodID); + result = functions->CallStaticDoubleMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jdouble CallStaticDoubleMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticDoubleMethodV(this,clazz,methodID,args); + } + jdouble CallStaticDoubleMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticDoubleMethodA(this,clazz,methodID,args); + } + + void CallStaticVoidMethod(jclass cls, jmethodID methodID, ...) { + va_list args; + va_start(args,methodID); + functions->CallStaticVoidMethodV(this,cls,methodID,args); + va_end(args); + } + void CallStaticVoidMethodV(jclass cls, jmethodID methodID, + va_list args) { + functions->CallStaticVoidMethodV(this,cls,methodID,args); + } + void CallStaticVoidMethodA(jclass cls, jmethodID methodID, + const jvalue * args) { + functions->CallStaticVoidMethodA(this,cls,methodID,args); + } + + jfieldID GetStaticFieldID(jclass clazz, const char *name, + const char *sig) { + return functions->GetStaticFieldID(this,clazz,name,sig); + } + jobject GetStaticObjectField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticObjectField(this,clazz,fieldID); + } + jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticBooleanField(this,clazz,fieldID); + } + jbyte GetStaticByteField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticByteField(this,clazz,fieldID); + } + jchar GetStaticCharField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticCharField(this,clazz,fieldID); + } + jshort GetStaticShortField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticShortField(this,clazz,fieldID); + } + jint GetStaticIntField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticIntField(this,clazz,fieldID); + } + jlong GetStaticLongField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticLongField(this,clazz,fieldID); + } + jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticFloatField(this,clazz,fieldID); + } + jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticDoubleField(this,clazz,fieldID); + } + + void SetStaticObjectField(jclass clazz, jfieldID fieldID, + jobject value) { + functions->SetStaticObjectField(this,clazz,fieldID,value); + } + void SetStaticBooleanField(jclass clazz, jfieldID fieldID, + jboolean value) { + functions->SetStaticBooleanField(this,clazz,fieldID,value); + } + void SetStaticByteField(jclass clazz, jfieldID fieldID, + jbyte value) { + functions->SetStaticByteField(this,clazz,fieldID,value); + } + void SetStaticCharField(jclass clazz, jfieldID fieldID, + jchar value) { + functions->SetStaticCharField(this,clazz,fieldID,value); + } + void SetStaticShortField(jclass clazz, jfieldID fieldID, + jshort value) { + functions->SetStaticShortField(this,clazz,fieldID,value); + } + void SetStaticIntField(jclass clazz, jfieldID fieldID, + jint value) { + functions->SetStaticIntField(this,clazz,fieldID,value); + } + void SetStaticLongField(jclass clazz, jfieldID fieldID, + jlong value) { + functions->SetStaticLongField(this,clazz,fieldID,value); + } + void SetStaticFloatField(jclass clazz, jfieldID fieldID, + jfloat value) { + functions->SetStaticFloatField(this,clazz,fieldID,value); + } + void SetStaticDoubleField(jclass clazz, jfieldID fieldID, + jdouble value) { + functions->SetStaticDoubleField(this,clazz,fieldID,value); + } + + jstring NewString(const jchar *unicode, jsize len) { + return functions->NewString(this,unicode,len); + } + jsize GetStringLength(jstring str) { + return functions->GetStringLength(this,str); + } + const jchar *GetStringChars(jstring str, jboolean *isCopy) { + return functions->GetStringChars(this,str,isCopy); + } + void ReleaseStringChars(jstring str, const jchar *chars) { + functions->ReleaseStringChars(this,str,chars); + } + + jstring NewStringUTF(const char *utf) { + return functions->NewStringUTF(this,utf); + } + jsize GetStringUTFLength(jstring str) { + return functions->GetStringUTFLength(this,str); + } + const char* GetStringUTFChars(jstring str, jboolean *isCopy) { + return functions->GetStringUTFChars(this,str,isCopy); + } + void ReleaseStringUTFChars(jstring str, const char* chars) { + functions->ReleaseStringUTFChars(this,str,chars); + } + + jsize GetArrayLength(jarray array) { + return functions->GetArrayLength(this,array); + } + + jobjectArray NewObjectArray(jsize len, jclass clazz, + jobject init) { + return functions->NewObjectArray(this,len,clazz,init); + } + jobject GetObjectArrayElement(jobjectArray array, jsize index) { + return functions->GetObjectArrayElement(this,array,index); + } + void SetObjectArrayElement(jobjectArray array, jsize index, + jobject val) { + functions->SetObjectArrayElement(this,array,index,val); + } + + jbooleanArray NewBooleanArray(jsize len) { + return functions->NewBooleanArray(this,len); + } + jbyteArray NewByteArray(jsize len) { + return functions->NewByteArray(this,len); + } + jcharArray NewCharArray(jsize len) { + return functions->NewCharArray(this,len); + } + jshortArray NewShortArray(jsize len) { + return functions->NewShortArray(this,len); + } + jintArray NewIntArray(jsize len) { + return functions->NewIntArray(this,len); + } + jlongArray NewLongArray(jsize len) { + return functions->NewLongArray(this,len); + } + jfloatArray NewFloatArray(jsize len) { + return functions->NewFloatArray(this,len); + } + jdoubleArray NewDoubleArray(jsize len) { + return functions->NewDoubleArray(this,len); + } + + jboolean * GetBooleanArrayElements(jbooleanArray array, jboolean *isCopy) { + return functions->GetBooleanArrayElements(this,array,isCopy); + } + jbyte * GetByteArrayElements(jbyteArray array, jboolean *isCopy) { + return functions->GetByteArrayElements(this,array,isCopy); + } + jchar * GetCharArrayElements(jcharArray array, jboolean *isCopy) { + return functions->GetCharArrayElements(this,array,isCopy); + } + jshort * GetShortArrayElements(jshortArray array, jboolean *isCopy) { + return functions->GetShortArrayElements(this,array,isCopy); + } + jint * GetIntArrayElements(jintArray array, jboolean *isCopy) { + return functions->GetIntArrayElements(this,array,isCopy); + } + jlong * GetLongArrayElements(jlongArray array, jboolean *isCopy) { + return functions->GetLongArrayElements(this,array,isCopy); + } + jfloat * GetFloatArrayElements(jfloatArray array, jboolean *isCopy) { + return functions->GetFloatArrayElements(this,array,isCopy); + } + jdouble * GetDoubleArrayElements(jdoubleArray array, jboolean *isCopy) { + return functions->GetDoubleArrayElements(this,array,isCopy); + } + + void ReleaseBooleanArrayElements(jbooleanArray array, + jboolean *elems, + jint mode) { + functions->ReleaseBooleanArrayElements(this,array,elems,mode); + } + void ReleaseByteArrayElements(jbyteArray array, + jbyte *elems, + jint mode) { + functions->ReleaseByteArrayElements(this,array,elems,mode); + } + void ReleaseCharArrayElements(jcharArray array, + jchar *elems, + jint mode) { + functions->ReleaseCharArrayElements(this,array,elems,mode); + } + void ReleaseShortArrayElements(jshortArray array, + jshort *elems, + jint mode) { + functions->ReleaseShortArrayElements(this,array,elems,mode); + } + void ReleaseIntArrayElements(jintArray array, + jint *elems, + jint mode) { + functions->ReleaseIntArrayElements(this,array,elems,mode); + } + void ReleaseLongArrayElements(jlongArray array, + jlong *elems, + jint mode) { + functions->ReleaseLongArrayElements(this,array,elems,mode); + } + void ReleaseFloatArrayElements(jfloatArray array, + jfloat *elems, + jint mode) { + functions->ReleaseFloatArrayElements(this,array,elems,mode); + } + void ReleaseDoubleArrayElements(jdoubleArray array, + jdouble *elems, + jint mode) { + functions->ReleaseDoubleArrayElements(this,array,elems,mode); + } + + void GetBooleanArrayRegion(jbooleanArray array, + jsize start, jsize len, jboolean *buf) { + functions->GetBooleanArrayRegion(this,array,start,len,buf); + } + void GetByteArrayRegion(jbyteArray array, + jsize start, jsize len, jbyte *buf) { + functions->GetByteArrayRegion(this,array,start,len,buf); + } + void GetCharArrayRegion(jcharArray array, + jsize start, jsize len, jchar *buf) { + functions->GetCharArrayRegion(this,array,start,len,buf); + } + void GetShortArrayRegion(jshortArray array, + jsize start, jsize len, jshort *buf) { + functions->GetShortArrayRegion(this,array,start,len,buf); + } + void GetIntArrayRegion(jintArray array, + jsize start, jsize len, jint *buf) { + functions->GetIntArrayRegion(this,array,start,len,buf); + } + void GetLongArrayRegion(jlongArray array, + jsize start, jsize len, jlong *buf) { + functions->GetLongArrayRegion(this,array,start,len,buf); + } + void GetFloatArrayRegion(jfloatArray array, + jsize start, jsize len, jfloat *buf) { + functions->GetFloatArrayRegion(this,array,start,len,buf); + } + void GetDoubleArrayRegion(jdoubleArray array, + jsize start, jsize len, jdouble *buf) { + functions->GetDoubleArrayRegion(this,array,start,len,buf); + } + + void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, + const jboolean *buf) { + functions->SetBooleanArrayRegion(this,array,start,len,buf); + } + void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, + const jbyte *buf) { + functions->SetByteArrayRegion(this,array,start,len,buf); + } + void SetCharArrayRegion(jcharArray array, jsize start, jsize len, + const jchar *buf) { + functions->SetCharArrayRegion(this,array,start,len,buf); + } + void SetShortArrayRegion(jshortArray array, jsize start, jsize len, + const jshort *buf) { + functions->SetShortArrayRegion(this,array,start,len,buf); + } + void SetIntArrayRegion(jintArray array, jsize start, jsize len, + const jint *buf) { + functions->SetIntArrayRegion(this,array,start,len,buf); + } + void SetLongArrayRegion(jlongArray array, jsize start, jsize len, + const jlong *buf) { + functions->SetLongArrayRegion(this,array,start,len,buf); + } + void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, + const jfloat *buf) { + functions->SetFloatArrayRegion(this,array,start,len,buf); + } + void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, + const jdouble *buf) { + functions->SetDoubleArrayRegion(this,array,start,len,buf); + } + + jint RegisterNatives(jclass clazz, const JNINativeMethod *methods, + jint nMethods) { + return functions->RegisterNatives(this,clazz,methods,nMethods); + } + jint UnregisterNatives(jclass clazz) { + return functions->UnregisterNatives(this,clazz); + } + + jint MonitorEnter(jobject obj) { + return functions->MonitorEnter(this,obj); + } + jint MonitorExit(jobject obj) { + return functions->MonitorExit(this,obj); + } + + jint GetJavaVM(JavaVM **vm) { + return functions->GetJavaVM(this,vm); + } + + void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf) { + functions->GetStringRegion(this,str,start,len,buf); + } + void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf) { + functions->GetStringUTFRegion(this,str,start,len,buf); + } + + void * GetPrimitiveArrayCritical(jarray array, jboolean *isCopy) { + return functions->GetPrimitiveArrayCritical(this,array,isCopy); + } + void ReleasePrimitiveArrayCritical(jarray array, void *carray, jint mode) { + functions->ReleasePrimitiveArrayCritical(this,array,carray,mode); + } + + const jchar * GetStringCritical(jstring string, jboolean *isCopy) { + return functions->GetStringCritical(this,string,isCopy); + } + void ReleaseStringCritical(jstring string, const jchar *cstring) { + functions->ReleaseStringCritical(this,string,cstring); + } + + jweak NewWeakGlobalRef(jobject obj) { + return functions->NewWeakGlobalRef(this,obj); + } + void DeleteWeakGlobalRef(jweak ref) { + functions->DeleteWeakGlobalRef(this,ref); + } + + jboolean ExceptionCheck() { + return functions->ExceptionCheck(this); + } + + jobject NewDirectByteBuffer(void* address, jlong capacity) { + return functions->NewDirectByteBuffer(this, address, capacity); + } + void* GetDirectBufferAddress(jobject buf) { + return functions->GetDirectBufferAddress(this, buf); + } + jlong GetDirectBufferCapacity(jobject buf) { + return functions->GetDirectBufferCapacity(this, buf); + } + jobjectRefType GetObjectRefType(jobject obj) { + return functions->GetObjectRefType(this, obj); + } + + /* Module Features */ + + jobject GetModule(jclass clazz) { + return functions->GetModule(this, clazz); + } + + /* Virtual threads */ + + jboolean IsVirtualThread(jobject obj) { + return functions->IsVirtualThread(this, obj); + } + +#endif /* __cplusplus */ +}; + +/* + * optionString may be any option accepted by the JVM, or one of the + * following: + * + * -D= Set a system property. + * -verbose[:class|gc|jni] Enable verbose output, comma-separated. E.g. + * "-verbose:class" or "-verbose:gc,class" + * Standard names include: gc, class, and jni. + * All nonstandard (VM-specific) names must begin + * with "X". + * vfprintf extraInfo is a pointer to the vfprintf hook. + * exit extraInfo is a pointer to the exit hook. + * abort extraInfo is a pointer to the abort hook. + */ +typedef struct JavaVMOption { + char *optionString; + void *extraInfo; +} JavaVMOption; + +typedef struct JavaVMInitArgs { + jint version; + + jint nOptions; + JavaVMOption *options; + jboolean ignoreUnrecognized; +} JavaVMInitArgs; + +typedef struct JavaVMAttachArgs { + jint version; + + char *name; + jobject group; +} JavaVMAttachArgs; + +/* These will be VM-specific. */ + +#define JDK1_2 +#define JDK1_4 + +/* End VM-specific. */ + +struct JNIInvokeInterface_ { + void *reserved0; + void *reserved1; + void *reserved2; + + jint (JNICALL *DestroyJavaVM)(JavaVM *vm); + + jint (JNICALL *AttachCurrentThread)(JavaVM *vm, void **penv, void *args); + + jint (JNICALL *DetachCurrentThread)(JavaVM *vm); + + jint (JNICALL *GetEnv)(JavaVM *vm, void **penv, jint version); + + jint (JNICALL *AttachCurrentThreadAsDaemon)(JavaVM *vm, void **penv, void *args); +}; + +struct JavaVM_ { + const struct JNIInvokeInterface_ *functions; +#ifdef __cplusplus + + jint DestroyJavaVM() { + return functions->DestroyJavaVM(this); + } + jint AttachCurrentThread(void **penv, void *args) { + return functions->AttachCurrentThread(this, penv, args); + } + jint DetachCurrentThread() { + return functions->DetachCurrentThread(this); + } + + jint GetEnv(void **penv, jint version) { + return functions->GetEnv(this, penv, version); + } + jint AttachCurrentThreadAsDaemon(void **penv, void *args) { + return functions->AttachCurrentThreadAsDaemon(this, penv, args); + } +#endif +}; + +#ifdef _JNI_IMPLEMENTATION_ +#define _JNI_IMPORT_OR_EXPORT_ JNIEXPORT +#else +#define _JNI_IMPORT_OR_EXPORT_ JNIIMPORT +#endif +_JNI_IMPORT_OR_EXPORT_ jint JNICALL +JNI_GetDefaultJavaVMInitArgs(void *args); + +_JNI_IMPORT_OR_EXPORT_ jint JNICALL +JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args); + +_JNI_IMPORT_OR_EXPORT_ jint JNICALL +JNI_GetCreatedJavaVMs(JavaVM **, jsize, jsize *); + +/* Defined by native libraries. */ +JNIEXPORT jint JNICALL +JNI_OnLoad(JavaVM *vm, void *reserved); + +JNIEXPORT void JNICALL +JNI_OnUnload(JavaVM *vm, void *reserved); + +#define JNI_VERSION_1_1 0x00010001 +#define JNI_VERSION_1_2 0x00010002 +#define JNI_VERSION_1_4 0x00010004 +#define JNI_VERSION_1_6 0x00010006 +#define JNI_VERSION_1_8 0x00010008 +#define JNI_VERSION_9 0x00090000 +#define JNI_VERSION_10 0x000a0000 +#define JNI_VERSION_19 0x00130000 +#define JNI_VERSION_20 0x00140000 +#define JNI_VERSION_21 0x00150000 + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_JAVASOFT_JNI_H_ */ diff --git a/netty-channel-unix-native/src/main/headers/jni_md.h b/netty-channel-unix-native/src/main/headers/jni_md.h new file mode 100644 index 0000000..6e583da --- /dev/null +++ b/netty-channel-unix-native/src/main/headers/jni_md.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 1996, 2020, 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. + */ + +#ifndef _JAVASOFT_JNI_MD_H_ +#define _JAVASOFT_JNI_MD_H_ + +#ifndef __has_attribute + #define __has_attribute(x) 0 +#endif + +#ifndef JNIEXPORT + #if (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4) && (__GNUC_MINOR__ > 2))) || __has_attribute(visibility) + #ifdef ARM + #define JNIEXPORT __attribute__((externally_visible,visibility("default"))) + #else + #define JNIEXPORT __attribute__((visibility("default"))) + #endif + #else + #define JNIEXPORT + #endif +#endif + +#if (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4) && (__GNUC_MINOR__ > 2))) || __has_attribute(visibility) + #ifdef ARM + #define JNIIMPORT __attribute__((externally_visible,visibility("default"))) + #else + #define JNIIMPORT __attribute__((visibility("default"))) + #endif +#else + #define JNIIMPORT +#endif + +#define JNICALL + +typedef int jint; +#ifdef _LP64 +typedef long jlong; +#else +typedef long long jlong; +#endif + +typedef signed char jbyte; + +#endif /* !_JAVASOFT_JNI_MD_H_ */ diff --git a/netty-channel-unix-native/src/main/headers/netty_jni_util.h b/netty-channel-unix-native/src/main/headers/netty_jni_util.h new file mode 100644 index 0000000..bba3f50 --- /dev/null +++ b/netty-channel-unix-native/src/main/headers/netty_jni_util.h @@ -0,0 +1,169 @@ +/* + * Copyright 2020 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#ifndef NETTY_JNI_UTIL_H_ +#define NETTY_JNI_UTIL_H_ + +#include + +#ifndef NETTY_JNI_UTIL_JNI_VERSION +#define NETTY_JNI_UTIL_JNI_VERSION JNI_VERSION_1_6 +#endif + +#define NETTY_JNI_UTIL_BEGIN_MACRO if (1) { +#define NETTY_JNI_UTIL_END_MACRO } else (void)(0) + +#define NETTY_JNI_UTIL_FIND_CLASS(E, C, N, R) \ + NETTY_JNI_UTIL_BEGIN_MACRO \ + C = (*(E))->FindClass((E), N); \ + if (C == NULL) { \ + (*(E))->ExceptionClear((E)); \ + goto R; \ + } \ + NETTY_JNI_UTIL_END_MACRO + +#define NETTY_JNI_UTIL_LOAD_CLASS(E, C, N, R) \ + NETTY_JNI_UTIL_BEGIN_MACRO \ + jclass _##C = (*(E))->FindClass((E), N); \ + if (_##C == NULL) { \ + (*(E))->ExceptionClear((E)); \ + goto R; \ + } \ + C = (*(E))->NewGlobalRef((E), _##C); \ + (*(E))->DeleteLocalRef((E), _##C); \ + if (C == NULL) { \ + goto R; \ + } \ + NETTY_JNI_UTIL_END_MACRO + +#define NETTY_JNI_UTIL_UNLOAD_CLASS(E, C) \ + NETTY_JNI_UTIL_BEGIN_MACRO \ + if (C != NULL) { \ + (*(E))->DeleteGlobalRef((E), (C)); \ + C = NULL; \ + } \ + NETTY_JNI_UTIL_END_MACRO + +#define NETTY_JNI_UTIL_LOAD_CLASS_WEAK(E, C, N, R) \ + NETTY_JNI_UTIL_BEGIN_MACRO \ + jclass _##C = (*(E))->FindClass((E), N); \ + if (_##C == NULL) { \ + (*(E))->ExceptionClear((E)); \ + goto R; \ + } \ + C = (*(E))->NewWeakGlobalRef((E), _##C); \ + (*(E))->DeleteLocalRef((E), _##C); \ + if (C == NULL) { \ + goto R; \ + } \ + NETTY_JNI_UTIL_END_MACRO + +#define NETTY_JNI_UTIL_UNLOAD_CLASS_WEAK(E, C) \ + NETTY_JNI_UTIL_BEGIN_MACRO \ + if (C != NULL) { \ + (*(E))->DeleteWeakGlobalRef((E), (C)); \ + C = NULL; \ + } \ + NETTY_JNI_UTIL_END_MACRO + + +#define NETTY_JNI_UTIL_NEW_LOCAL_FROM_WEAK(E, C, W, R) \ + NETTY_JNI_UTIL_BEGIN_MACRO \ + C = (*(E))->NewLocalRef((E), W); \ + if ((*(E))->IsSameObject((E), C, NULL) || C == NULL) { \ + goto R; \ + } \ + NETTY_JNI_UTIL_END_MACRO + +#define NETTY_JNI_UTIL_DELETE_LOCAL(E, L) \ + NETTY_JNI_UTIL_BEGIN_MACRO \ + if (L != NULL) { \ + (*(E))->DeleteLocalRef((E), L); \ + } \ + NETTY_JNI_UTIL_END_MACRO + +#define NETTY_JNI_UTIL_GET_METHOD(E, C, M, N, S, R) \ + NETTY_JNI_UTIL_BEGIN_MACRO \ + M = (*(E))->GetMethodID((E), C, N, S); \ + if (M == NULL) { \ + goto R; \ + } \ + NETTY_JNI_UTIL_END_MACRO + +#define NETTY_JNI_UTIL_GET_FIELD(E, C, F, N, S, R) \ + NETTY_JNI_UTIL_BEGIN_MACRO \ + F = (*(E))->GetFieldID((E), C, N, S); \ + if (F == NULL) { \ + goto R; \ + } \ + NETTY_JNI_UTIL_END_MACRO + +#define NETTY_JNI_UTIL_TRY_GET_FIELD(E, C, F, N, S) \ + NETTY_JNI_UTIL_BEGIN_MACRO \ + F = (*(E))->GetFieldID((E), C, N, S); \ + if (F == NULL) { \ + (*(E))->ExceptionClear((E)); \ + } \ + NETTY_JNI_UTIL_END_MACRO + +#define NETTY_JNI_UTIL_PREPEND(P, S, N, R) \ + NETTY_JNI_UTIL_BEGIN_MACRO \ + if ((N = netty_jni_util_prepend(P, S)) == NULL) { \ + goto R; \ + } \ + NETTY_JNI_UTIL_END_MACRO + +/** + * Return a new string (caller must free this string) which is equivalent to
prefix + str
. + * + * Caller must free the return value! + */ +char* netty_jni_util_prepend(const char* prefix, const char* str); + +char* netty_jni_util_rstrstr(char* s1rbegin, const char* s1rend, const char* s2); + +/** + * The expected format of the library name is "lib<>$libraryName" where the <> portion is what we will return. + * If status != JNI_ERR then the caller MUST call free on the return value. + */ +char* netty_jni_util_parse_package_prefix(const char* libraryPathName, const char* libraryName, jint* status); + +/** + * Return type is as defined in https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#wp5833. + */ +jint netty_jni_util_register_natives(JNIEnv* env, const char* packagePrefix, const char* className, const JNINativeMethod* methods, jint numMethods); +jint netty_jni_util_unregister_natives(JNIEnv* env, const char* packagePrefix, const char* className); + +/** + * Free a dynamic allocated methods table + */ +void netty_jni_util_free_dynamic_methods_table(JNINativeMethod* dynamicMethods, jint fixedMethodTableSize, jint fullMethodTableSize); +/** + * Free dynamic allocated name + */ +void netty_jni_util_free_dynamic_name(char** dynamicName); + +/** + * Function should be called when the native library is loaded. load_function takes ownership of packagePrefix. + */ +jint netty_jni_util_JNI_OnLoad(JavaVM* vm, void* reserved, const char* libname, jint (*load_function)(JNIEnv* env, const char* packagePrefix)); + +/** + * Function should be called when the native library is unloaded + */ +void netty_jni_util_JNI_OnUnload(JavaVM* vm, void* reserved, void (*unload_function)(JNIEnv* env)); + +#endif /* NETTY_JNI_UTIL_H_ */ diff --git a/netty-channel-unix-native/src/main/headers/netty_unix.h b/netty-channel-unix-native/src/main/headers/netty_unix.h new file mode 100644 index 0000000..dff6da7 --- /dev/null +++ b/netty-channel-unix-native/src/main/headers/netty_unix.h @@ -0,0 +1,25 @@ +/* + * Copyright 2020 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#ifndef NETTY_UNIX_H_ +#define NETTY_UNIX_H_ + +#include + +// JNI initialization hooks. +jint netty_unix_register(JNIEnv* env, const char* packagePrefix); +void netty_unix_unregister(JNIEnv* env, const char* packagePrefix); + +#endif /* NETTY_UNIX_H_ */ diff --git a/netty-channel-unix-native/src/main/headers/netty_unix_buffer.h b/netty-channel-unix-native/src/main/headers/netty_unix_buffer.h new file mode 100644 index 0000000..6444f5f --- /dev/null +++ b/netty-channel-unix-native/src/main/headers/netty_unix_buffer.h @@ -0,0 +1,25 @@ +/* + * Copyright 2018 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#ifndef NETTY_UNIX_BUFFER_H_ +#define NETTY_UNIX_BUFFER_H_ + +#include + +// JNI initialization hooks. Users of this file are responsible for calling these in the JNI_OnLoad and JNI_OnUnload methods. +jint netty_unix_buffer_JNI_OnLoad(JNIEnv* env, const char* packagePrefix); +void netty_unix_buffer_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix); + +#endif /* NETTY_UNIX_BUFFER_H_ */ diff --git a/netty-channel-unix-native/src/main/headers/netty_unix_errors.h b/netty-channel-unix-native/src/main/headers/netty_unix_errors.h new file mode 100644 index 0000000..8ea8e4c --- /dev/null +++ b/netty-channel-unix-native/src/main/headers/netty_unix_errors.h @@ -0,0 +1,34 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#ifndef NETTY_UNIX_ERRORS_H_ +#define NETTY_UNIX_ERRORS_H_ + +#include + +void netty_unix_errors_throwRuntimeException(JNIEnv* env, char* message); +void netty_unix_errors_throwRuntimeExceptionErrorNo(JNIEnv* env, char* message, int errorNumber); +void netty_unix_errors_throwChannelExceptionErrorNo(JNIEnv* env, char* message, int errorNumber); +void netty_unix_errors_throwIOException(JNIEnv* env, char* message); +void netty_unix_errors_throwIOExceptionErrorNo(JNIEnv* env, char* message, int errorNumber); +void netty_unix_errors_throwPortUnreachableException(JNIEnv* env, char* message); +void netty_unix_errors_throwClosedChannelException(JNIEnv* env); +void netty_unix_errors_throwOutOfMemoryError(JNIEnv* env); + +// JNI initialization hooks. Users of this file are responsible for calling these in the JNI_OnLoad and JNI_OnUnload methods. +jint netty_unix_errors_JNI_OnLoad(JNIEnv* env, const char* packagePrefix); +void netty_unix_errors_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix); + +#endif /* NETTY_UNIX_ERRORS_H_ */ diff --git a/netty-channel-unix-native/src/main/headers/netty_unix_filedescriptor.h b/netty-channel-unix-native/src/main/headers/netty_unix_filedescriptor.h new file mode 100644 index 0000000..8c83e52 --- /dev/null +++ b/netty-channel-unix-native/src/main/headers/netty_unix_filedescriptor.h @@ -0,0 +1,25 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#ifndef NETTY_UNIX_FILEDESCRIPTOR_H_ +#define NETTY_UNIX_FILEDESCRIPTOR_H_ + +#include + +// JNI initialization hooks. Users of this file are responsible for calling these in the JNI_OnLoad and JNI_OnUnload methods. +jint netty_unix_filedescriptor_JNI_OnLoad(JNIEnv* env, const char* packagePrefix); +void netty_unix_filedescriptor_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix); + +#endif /* NETTY_UNIX_FILEDESCRIPTOR_H_ */ diff --git a/netty-channel-unix-native/src/main/headers/netty_unix_jni.h b/netty-channel-unix-native/src/main/headers/netty_unix_jni.h new file mode 100644 index 0000000..2588894 --- /dev/null +++ b/netty-channel-unix-native/src/main/headers/netty_unix_jni.h @@ -0,0 +1,21 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#ifndef NETTY_UNIX_JNI_H_ +#define NETTY_UNIX_JNI_H_ + +#include + +#endif /* NETTY_UNIX_JNI_H_ */ diff --git a/netty-channel-unix-native/src/main/headers/netty_unix_limits.h b/netty-channel-unix-native/src/main/headers/netty_unix_limits.h new file mode 100644 index 0000000..5b3d24e --- /dev/null +++ b/netty-channel-unix-native/src/main/headers/netty_unix_limits.h @@ -0,0 +1,25 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#ifndef NETTY_UNIX_LIMITS_H_ +#define NETTY_UNIX_LIMITS_H_ + +#include + +// JNI initialization hooks. Users of this file are responsible for calling these in the JNI_OnLoad and JNI_OnUnload methods. +jint netty_unix_limits_JNI_OnLoad(JNIEnv* env, const char* packagePrefix); +void netty_unix_limits_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix); + +#endif /* NETTY_UNIX_LIMITS_H_ */ diff --git a/netty-channel-unix-native/src/main/headers/netty_unix_socket.h b/netty-channel-unix-native/src/main/headers/netty_unix_socket.h new file mode 100644 index 0000000..32b9ae6 --- /dev/null +++ b/netty-channel-unix-native/src/main/headers/netty_unix_socket.h @@ -0,0 +1,40 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#ifndef NETTY_UNIX_SOCKET_H_ +#define NETTY_UNIX_SOCKET_H_ + +#include +#include + +// External C methods +int netty_unix_socket_nonBlockingSocket(int domain, int type, int protocol); +int netty_unix_socket_initSockaddr(JNIEnv* env, jboolean ipv6, jbyteArray address, jint scopeId, jint jport, const struct sockaddr_storage* addr, socklen_t* addrSize); +jbyteArray netty_unix_socket_createInetSocketAddressArray(JNIEnv* env, const struct sockaddr_storage* addr); + +int netty_unix_socket_getOption(JNIEnv* env, jint fd, int level, int optname, void* optval, socklen_t optlen); +int netty_unix_socket_setOption(JNIEnv* env, jint fd, int level, int optname, const void* optval, socklen_t len); +int netty_unix_socket_ipAddressLength(const struct sockaddr_storage* addr); + +// These method is sometimes needed if you want to special handle some errno value before throwing an exception. +int netty_unix_socket_getOption0(jint fd, int level, int optname, void* optval, socklen_t optlen); +void netty_unix_socket_getOptionHandleError(JNIEnv* env, int err); + + +// JNI initialization hooks. Users of this file are responsible for calling these in the JNI_OnLoad and JNI_OnUnload methods. +jint netty_unix_socket_JNI_OnLoad(JNIEnv* env, const char* packagePrefix); +void netty_unix_socket_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix); + +#endif /* NETTY_UNIX_SOCKET_H_ */ diff --git a/netty-channel-unix-native/src/main/headers/netty_unix_util.h b/netty-channel-unix-native/src/main/headers/netty_unix_util.h new file mode 100644 index 0000000..c0529ec --- /dev/null +++ b/netty-channel-unix-native/src/main/headers/netty_unix_util.h @@ -0,0 +1,69 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#ifndef NETTY_UNIX_UTIL_H_ +#define NETTY_UNIX_UTIL_H_ + +#include +#include +#include +#include "netty_jni_util.h" + + +#if defined(__MACH__) && !defined(CLOCK_REALTIME) +#define NETTY_USE_MACH_INSTEAD_OF_CLOCK + +typedef int clockid_t; + +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC 1 +#endif + +#ifndef CLOCK_MONOTONIC_COARSE +#define CLOCK_MONOTONIC_COARSE 2 +#endif + +#endif /* __MACH__ */ + +/** + * Get a clock which can be used to measure execution time. + * + * Returns true is a suitable clock was found. + */ +jboolean netty_unix_util_initialize_wait_clock(clockid_t* clockId); + +/** + * This will delegate to clock_gettime from time.h if the platform supports it. + * + * MacOS does not support clock_gettime. + */ +int netty_unix_util_clock_gettime(clockid_t clockId, struct timespec* tp); + +/** + * Calculate the number of nano seconds elapsed between begin and end. + * + * Returns the number of nano seconds. + */ +uint64_t netty_unix_util_timespec_elapsed_ns(const struct timespec* begin, const struct timespec* end); + +/** + * Subtract
nanos
nano seconds from a
timespec
. + * + * Returns true if there is underflow. + */ +jboolean netty_unix_util_timespec_subtract_ns(struct timespec* ts, uint64_t nanos); + +#endif /* NETTY_UNIX_UTIL_H_ */ diff --git a/netty-channel-unix/build.gradle b/netty-channel-unix/build.gradle index d4c655e..33bbcbd 100644 --- a/netty-channel-unix/build.gradle +++ b/netty-channel-unix/build.gradle @@ -1,3 +1,5 @@ + + dependencies { api project(':netty-buffer') api project(':netty-channel') diff --git a/netty-handler-codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/netty-handler-codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java index 18dfde4..282796f 100644 --- a/netty-handler-codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +++ b/netty-handler-codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java @@ -903,15 +903,18 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { if (b == ';' || isControlOrWhitespaceAsciiChar(b)) { if (i == 0) { // empty case - throw new NumberFormatException(); + throw new NumberFormatException("Empty chunk size"); } return result; } // non-hex char fail-fast path - throw new NumberFormatException(); + throw new NumberFormatException("Invalid character in chunk size"); } result *= 16; result += digit; + if (result < 0) { + throw new NumberFormatException("Chunk size overflow: " + result); + } } return result; } diff --git a/netty-handler-codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java b/netty-handler-codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java index 35a0def..6a6d358 100644 --- a/netty-handler-codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java +++ b/netty-handler-codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java @@ -35,6 +35,7 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -639,6 +640,38 @@ public class HttpRequestDecoderTest { assertFalse(channel.finish()); } + @Test + public void testChunkSizeOverflow() { + String requestStr = "PUT /some/path HTTP/1.1\r\n" + + "Transfer-Encoding: chunked\r\n\r\n" + + "8ccccccc\r\n"; + EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder()); + assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII))); + HttpRequest request = channel.readInbound(); + assertTrue(request.decoderResult().isSuccess()); + HttpContent c = channel.readInbound(); + c.release(); + assertTrue(c.decoderResult().isFailure()); + assertInstanceOf(NumberFormatException.class, c.decoderResult().cause()); + assertFalse(channel.finish()); + } + + @Test + public void testChunkSizeOverflow2() { + String requestStr = "PUT /some/path HTTP/1.1\r\n" + + "Transfer-Encoding: chunked\r\n\r\n" + + "bbbbbbbe;\n\r\n"; + EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder()); + assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII))); + HttpRequest request = channel.readInbound(); + assertTrue(request.decoderResult().isSuccess()); + HttpContent c = channel.readInbound(); + c.release(); + assertTrue(c.decoderResult().isFailure()); + assertInstanceOf(NumberFormatException.class, c.decoderResult().cause()); + assertFalse(channel.finish()); + } + private static void testInvalidHeaders0(String requestStr) { testInvalidHeaders0(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)); } diff --git a/netty-handler-codec-http2/build.gradle b/netty-handler-codec-http2/build.gradle index 9e41ef9..eafde80 100644 --- a/netty-handler-codec-http2/build.gradle +++ b/netty-handler-codec-http2/build.gradle @@ -4,6 +4,6 @@ dependencies { testImplementation testLibs.assertj testImplementation testLibs.mockito.core testImplementation project(':netty-handler-ssl-bouncycastle') - testRuntimeOnly project(path: ':netty-tcnative-boringssl-static', configuration: osdetector.classifier) + testRuntimeOnly project(path: ':netty-tcnative-boringssl-static-native', configuration: osdetector.classifier) testRuntimeOnly libs.brotli4j.native."${osdetector.os}"."${osdetector.arch.replace('_','')}" } diff --git a/netty-handler-codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2StreamChannel.java b/netty-handler-codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2StreamChannel.java index 49882ac..c768576 100644 --- a/netty-handler-codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2StreamChannel.java +++ b/netty-handler-codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2StreamChannel.java @@ -605,7 +605,7 @@ abstract class AbstractHttp2StreamChannel extends DefaultAttributeMap implements if (allocHandle.continueReading()) { maybeAddChannelToReadCompletePendingQueue(); } else { - unsafe.notifyReadComplete(allocHandle, true); + unsafe.notifyReadComplete(allocHandle, true, false); } } else { if (inboundBuffer == null) { @@ -618,7 +618,7 @@ abstract class AbstractHttp2StreamChannel extends DefaultAttributeMap implements void fireChildReadComplete() { assert eventLoop().inEventLoop(); assert readStatus != ReadStatus.IDLE || !readCompletePending; - unsafe.notifyReadComplete(unsafe.recvBufAllocHandle(), false); + unsafe.notifyReadComplete(unsafe.recvBufAllocHandle(), false, false); } final void closeWithError(Http2Error error) { @@ -851,35 +851,47 @@ abstract class AbstractHttp2StreamChannel extends DefaultAttributeMap implements } void doBeginRead() { - // Process messages until there are none left (or the user stopped requesting) and also handle EOS. - while (readStatus != ReadStatus.IDLE) { - Object message = pollQueuedMessage(); - if (message == null) { - if (readEOS) { - unsafe.closeForcibly(); - } - // We need to double check that there is nothing left to flush such as a - // window update frame. + if (readStatus == ReadStatus.IDLE) { + // Don't wait for the user to request a read to notify of channel closure. + if (readEOS && (inboundBuffer == null || inboundBuffer.isEmpty())) { + // Double check there is nothing left to flush such as a window update frame. flush(); - break; + unsafe.closeForcibly(); } - final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle(); - allocHandle.reset(config()); - boolean continueReading = false; - do { - doRead0((Http2Frame) message, allocHandle); - } while ((readEOS || (continueReading = allocHandle.continueReading())) - && (message = pollQueuedMessage()) != null); + } else { + do { // Process messages until there are none left (or the user stopped requesting) and also handle EOS. + Object message = pollQueuedMessage(); + if (message == null) { + // Double check there is nothing left to flush such as a window update frame. + flush(); + if (readEOS) { + unsafe.closeForcibly(); + } + break; + } + final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle(); + allocHandle.reset(config()); + boolean continueReading = false; + do { + doRead0((Http2Frame) message, allocHandle); + } while ((readEOS || (continueReading = allocHandle.continueReading())) + && (message = pollQueuedMessage()) != null); - if (continueReading && isParentReadInProgress() && !readEOS) { - // Currently the parent and child channel are on the same EventLoop thread. If the parent is - // currently reading it is possible that more frames will be delivered to this child channel. In - // the case that this child channel still wants to read we delay the channelReadComplete on this - // child channel until the parent is done reading. - maybeAddChannelToReadCompletePendingQueue(); - } else { - notifyReadComplete(allocHandle, true); - } + if (continueReading && isParentReadInProgress() && !readEOS) { + // Currently the parent and child channel are on the same EventLoop thread. If the parent is + // currently reading it is possible that more frames will be delivered to this child channel. In + // the case that this child channel still wants to read we delay the channelReadComplete on this + // child channel until the parent is done reading. + maybeAddChannelToReadCompletePendingQueue(); + } else { + notifyReadComplete(allocHandle, true, true); + + // While in the read loop reset the readState AFTER calling readComplete (or other pipeline + // callbacks) to prevents re-entry into this method (if autoRead is disabled and the user calls + // read on each readComplete) and StackOverflowException. + resetReadStatus(); + } + } while (readStatus != ReadStatus.IDLE); } } @@ -908,17 +920,21 @@ abstract class AbstractHttp2StreamChannel extends DefaultAttributeMap implements } } - void notifyReadComplete(RecvByteBufAllocator.Handle allocHandle, boolean forceReadComplete) { + private void resetReadStatus() { + readStatus = readStatus == ReadStatus.REQUESTED ? ReadStatus.IN_PROGRESS : ReadStatus.IDLE; + } + + void notifyReadComplete(RecvByteBufAllocator.Handle allocHandle, boolean forceReadComplete, + boolean inReadLoop) { if (!readCompletePending && !forceReadComplete) { return; } // Set to false just in case we added the channel multiple times before. readCompletePending = false; - if (readStatus == ReadStatus.REQUESTED) { - readStatus = ReadStatus.IN_PROGRESS; - } else { - readStatus = ReadStatus.IDLE; + if (!inReadLoop) { + // While in the read loop we reset the state after calling pipeline methods to prevent StackOverflow. + resetReadStatus(); } allocHandle.readComplete(); diff --git a/netty-handler-codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java b/netty-handler-codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java index 60a2925..eafb263 100644 --- a/netty-handler-codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java +++ b/netty-handler-codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java @@ -24,8 +24,8 @@ import io.netty.util.internal.UnstableApi; import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; import static io.netty.handler.codec.http2.Http2Exception.connectionError; -import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.getPseudoHeader; import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.hasPseudoHeaderFormat; +import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.isPseudoHeader; import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER; import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER; import static io.netty.util.AsciiString.isUpperCase; @@ -47,6 +47,15 @@ public class DefaultHttp2Headers "empty headers are not allowed [%s]", name)); } + if (hasPseudoHeaderFormat(name)) { + if (!isPseudoHeader(name)) { + PlatformDependent.throwException(connectionError( + PROTOCOL_ERROR, "Invalid HTTP/2 pseudo-header '%s' encountered.", name)); + } + // no need for lower-case validation, we trust our own pseudo header constants + return; + } + if (name instanceof AsciiString) { final int index; try { @@ -72,14 +81,6 @@ public class DefaultHttp2Headers } } } - - if (hasPseudoHeaderFormat(name)) { - final Http2Headers.PseudoHeaderName pseudoHeader = getPseudoHeader(name); - if (pseudoHeader == null) { - PlatformDependent.throwException(connectionError( - PROTOCOL_ERROR, "Invalid HTTP/2 pseudo-header '%s' encountered.", name)); - } - } } }; diff --git a/netty-handler-codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexTest.java b/netty-handler-codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexTest.java index f277dbb..b1f28b1 100644 --- a/netty-handler-codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexTest.java +++ b/netty-handler-codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexTest.java @@ -58,6 +58,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import static io.netty.handler.codec.http2.Http2Error.NO_ERROR; import static io.netty.handler.codec.http2.Http2TestUtil.anyChannelPromise; import static io.netty.handler.codec.http2.Http2TestUtil.anyHttp2Settings; import static io.netty.handler.codec.http2.Http2TestUtil.assertEqualsAndRelease; @@ -477,6 +478,74 @@ public abstract class Http2MultiplexTest { verifyFramesMultiplexedToCorrectChannel(childChannel, inboundHandler, 2); } + @Test + public void noAutoReadWithReentrantReadDoesNotSOOE() { + final AtomicBoolean shouldRead = new AtomicBoolean(); + Consumer ctxConsumer = new Consumer() { + @Override + public void accept(ChannelHandlerContext obj) { + if (shouldRead.get()) { + obj.read(); + } + } + }; + LastInboundHandler inboundHandler = new LastInboundHandler(ctxConsumer); + AtomicInteger maxReads = new AtomicInteger(1); + Http2StreamChannel childChannel = newInboundStream(3, false, maxReads, inboundHandler); + assertTrue(childChannel.config().isAutoRead()); + Http2HeadersFrame headersFrame = inboundHandler.readInbound(); + assertNotNull(headersFrame); + + childChannel.config().setAutoRead(false); + + final int maxWrites = 10000; // enough writes to generated SOOE. + for (int i = 0; i < maxWrites; ++i) { + frameInboundWriter.writeInboundData(childChannel.stream().id(), bb(String.valueOf(i)), 0, false); + } + frameInboundWriter.writeInboundData(childChannel.stream().id(), bb(String.valueOf(maxWrites)), 0, true); + shouldRead.set(true); + childChannel.read(); + + for (int i = 0; i < maxWrites; ++i) { + Http2DataFrame dataFrame0 = inboundHandler.readInbound(); + assertNotNull(dataFrame0); + release(dataFrame0); + } + Http2DataFrame dataFrame0 = inboundHandler.readInbound(); + assertTrue(dataFrame0.isEndStream()); + release(dataFrame0); + + verifyFramesMultiplexedToCorrectChannel(childChannel, inboundHandler, 0); + } + + @Test + public void readNotRequiredToEndStream() { + LastInboundHandler inboundHandler = new LastInboundHandler(); + AtomicInteger maxReads = new AtomicInteger(1); + Http2StreamChannel childChannel = newInboundStream(3, false, maxReads, inboundHandler); + assertTrue(childChannel.config().isAutoRead()); + + childChannel.config().setAutoRead(false); + + Http2HeadersFrame headersFrame = inboundHandler.readInbound(); + assertNotNull(headersFrame); + + assertNull(inboundHandler.readInbound()); + + frameInboundWriter.writeInboundRstStream(childChannel.stream().id(), NO_ERROR.code()); + + assertFalse(inboundHandler.isChannelActive()); + childChannel.closeFuture().syncUninterruptibly(); + + Http2ResetFrame resetFrame = useUserEventForResetFrame() ? inboundHandler.readUserEvent() : + inboundHandler.readInbound(); + + assertEquals(childChannel.stream(), resetFrame.stream()); + assertEquals(NO_ERROR.code(), resetFrame.errorCode()); + + verifyFramesMultiplexedToCorrectChannel(childChannel, inboundHandler, 0); + } + @Test public void channelReadShouldRespectAutoReadAndNotProduceNPE() throws Exception { LastInboundHandler inboundHandler = new LastInboundHandler(); @@ -1095,7 +1164,7 @@ public abstract class Http2MultiplexTest { childChannel.config().setAutoRead(true); numReads.set(1); - frameInboundWriter.writeInboundRstStream(childChannel.stream().id(), Http2Error.NO_ERROR.code()); + frameInboundWriter.writeInboundRstStream(childChannel.stream().id(), NO_ERROR.code()); // Detecting EOS should flush all pending data regardless of read calls. assertEqualsAndRelease(dataFrame2, inboundHandler.readInbound()); @@ -1111,7 +1180,7 @@ public abstract class Http2MultiplexTest { inboundHandler.readInbound(); assertEquals(childChannel.stream(), resetFrame.stream()); - assertEquals(Http2Error.NO_ERROR.code(), resetFrame.errorCode()); + assertEquals(NO_ERROR.code(), resetFrame.errorCode()); assertNull(inboundHandler.readInbound()); diff --git a/netty-handler-codec-quic-native/build.gradle b/netty-handler-codec-quic-native/build.gradle index 19542b9..e875605 100644 --- a/netty-handler-codec-quic-native/build.gradle +++ b/netty-handler-codec-quic-native/build.gradle @@ -3,17 +3,17 @@ task nettyQuicLinuxX8664(type: Jar) { archiveBaseName.set('netty-handler-codec-quic-native') archiveClassifier.set('linux-x86_64') version rootProject.version - from (sourceSets.main.output) { + from (project.layout.projectDirectory.dir('src/main/resources')) { include 'META-INF/native/libnetty_quiche_linux_x86_64.so' } } -assemble.dependsOn(nettyQuicLinuxX8664) +//assemble.dependsOn(nettyQuicLinuxX8664) configurations { 'linux-x86_64' { canBeConsumed = true canBeResolved = false - extendsFrom runtimeOnly + //extendsFrom runtimeOnly } } diff --git a/netty-handler-ssl/build.gradle b/netty-handler-ssl/build.gradle index d61d678..5bd18b0 100644 --- a/netty-handler-ssl/build.gradle +++ b/netty-handler-ssl/build.gradle @@ -14,5 +14,5 @@ dependencies { classifier = osdetector.classifier } } - testRuntimeOnly project(path: ':netty-tcnative-boringssl-static', configuration: osdetector.classifier) + testRuntimeOnly project(path: ':netty-tcnative-boringssl-static-native', configuration: osdetector.classifier) } diff --git a/netty-tcnative-boringssl-static/build.gradle b/netty-tcnative-boringssl-static-native/build.gradle similarity index 82% rename from netty-tcnative-boringssl-static/build.gradle rename to netty-tcnative-boringssl-static-native/build.gradle index e3b82da..89218cd 100644 --- a/netty-tcnative-boringssl-static/build.gradle +++ b/netty-tcnative-boringssl-static-native/build.gradle @@ -3,77 +3,82 @@ task nettyTcNativeBoringSslStaticLinuxX8664(type: Jar) { archiveBaseName.set('netty-tcnative-boringssl-static') archiveClassifier.set('linux-x86_64') version rootProject.version - from (sourceSets.main.output) { + //from (sourceSets.main.output) { + from (project.layout.projectDirectory.dir('src/main/resources')) { include 'META-INF/native/libnetty_tcnative_linux_x86_64.so' } } -assemble.dependsOn(nettyTcNativeBoringSslStaticLinuxX8664) +//assemble.dependsOn(nettyTcNativeBoringSslStaticLinuxX8664) task nettyTcNativeBoringSslStaticLinuxAarch64(type: Jar) { archiveBaseName.set('netty-tcnative-boringssl-static') archiveClassifier.set('linux-aarch_64') version rootProject.version - from (sourceSets.main.output) { + //from (sourceSets.main.output) { + from (project.layout.projectDirectory.dir('src/main/resources')) { include 'META-INF/native/libnetty_tcnative_linux_aarch_64.so' } } -assemble.dependsOn(nettyTcNativeBoringSslStaticLinuxAarch64) +//assemble.dependsOn(nettyTcNativeBoringSslStaticLinuxAarch64) task nettyTcNativeBoringSslStaticOsxX8664(type: Jar) { archiveBaseName.set('netty-tcnative-boringssl-static') archiveClassifier.set('osx-x86_64') version rootProject.version - from (sourceSets.main.output) { + //from (sourceSets.main.output) { + from (project.layout.projectDirectory.dir('src/main/resources')) { include 'META-INF/native/libnetty_tcnative_osx_x86_64.jnilib' } } -assemble.dependsOn(nettyTcNativeBoringSslStaticOsxX8664) +//assemble.dependsOn(nettyTcNativeBoringSslStaticOsxX8664) task nettyTcNativeBoringSslStaticOsxAarch64(type: Jar) { archiveBaseName.set('netty-tcnative-boringssl-static') archiveClassifier.set('osx-aarch_64') version rootProject.version - from (sourceSets.main.output) { + //from (sourceSets.main.output) { + from (project.layout.projectDirectory.dir('src/main/resources')) { include 'META-INF/native/libnetty_tcnative_osx_aarch_64.jnilib' } } -assemble.dependsOn(nettyTcNativeBoringSslStaticOsxAarch64) +//assemble.dependsOn(nettyTcNativeBoringSslStaticOsxAarch64) task nettyTcNativeBoringSslStaticWindowsX8664(type: Jar) { archiveBaseName.set('netty-tcnative-boringssl-static') archiveClassifier.set('windows-x86_64') version rootProject.version - from (sourceSets.main.output) { + //from (sourceSets.main.output) { + from (project.layout.projectDirectory.dir('src/main/resources')) { include 'META-INF/native/libnetty_tcnative_windows_x86_64.dll' } } -assemble.dependsOn(nettyTcNativeBoringSslStaticWindowsX8664) +//assemble.dependsOn(nettyTcNativeBoringSslStaticWindowsX8664) configurations { 'linux-x86_64' { canBeConsumed = true canBeResolved = false - extendsFrom runtimeOnly + //extendsFrom runtimeOnly } 'linux-aarch64' { canBeConsumed = true canBeResolved = false - extendsFrom runtimeOnly + //extendsFrom runtimeOnly } 'osx-x86_64' { canBeConsumed = true canBeResolved = false - extendsFrom runtimeOnly + //extendsFrom runtimeOnly } 'osx-aarch64' { canBeConsumed = true canBeResolved = false - extendsFrom runtimeOnly + //extendsFrom runtimeOnly } 'windows-x86_64' { canBeConsumed = true canBeResolved = false - extendsFrom runtimeOnly + //extendsFrom runtimeOnly } } diff --git a/netty-tcnative-boringssl-static/src/main/resources/META-INF/native/libnetty_tcnative_linux_aarch_64.so b/netty-tcnative-boringssl-static-native/src/main/resources/META-INF/native/libnetty_tcnative_linux_aarch_64.so similarity index 100% rename from netty-tcnative-boringssl-static/src/main/resources/META-INF/native/libnetty_tcnative_linux_aarch_64.so rename to netty-tcnative-boringssl-static-native/src/main/resources/META-INF/native/libnetty_tcnative_linux_aarch_64.so diff --git a/netty-tcnative-boringssl-static/src/main/resources/META-INF/native/libnetty_tcnative_linux_x86_64.so b/netty-tcnative-boringssl-static-native/src/main/resources/META-INF/native/libnetty_tcnative_linux_x86_64.so similarity index 100% rename from netty-tcnative-boringssl-static/src/main/resources/META-INF/native/libnetty_tcnative_linux_x86_64.so rename to netty-tcnative-boringssl-static-native/src/main/resources/META-INF/native/libnetty_tcnative_linux_x86_64.so diff --git a/netty-tcnative-boringssl-static/src/main/resources/META-INF/native/libnetty_tcnative_osx_aarch_64.jnilib b/netty-tcnative-boringssl-static-native/src/main/resources/META-INF/native/libnetty_tcnative_osx_aarch_64.jnilib similarity index 100% rename from netty-tcnative-boringssl-static/src/main/resources/META-INF/native/libnetty_tcnative_osx_aarch_64.jnilib rename to netty-tcnative-boringssl-static-native/src/main/resources/META-INF/native/libnetty_tcnative_osx_aarch_64.jnilib diff --git a/netty-tcnative-boringssl-static/src/main/resources/META-INF/native/libnetty_tcnative_osx_x86_64.jnilib b/netty-tcnative-boringssl-static-native/src/main/resources/META-INF/native/libnetty_tcnative_osx_x86_64.jnilib similarity index 100% rename from netty-tcnative-boringssl-static/src/main/resources/META-INF/native/libnetty_tcnative_osx_x86_64.jnilib rename to netty-tcnative-boringssl-static-native/src/main/resources/META-INF/native/libnetty_tcnative_osx_x86_64.jnilib diff --git a/netty-tcnative-boringssl-static/src/main/resources/META-INF/native/netty_tcnative_windows_x86_64.dll b/netty-tcnative-boringssl-static-native/src/main/resources/META-INF/native/netty_tcnative_windows_x86_64.dll similarity index 100% rename from netty-tcnative-boringssl-static/src/main/resources/META-INF/native/netty_tcnative_windows_x86_64.dll rename to netty-tcnative-boringssl-static-native/src/main/resources/META-INF/native/netty_tcnative_windows_x86_64.dll diff --git a/patches/13735.patch b/patches/13735.patch new file mode 100644 index 0000000..8e3403e --- /dev/null +++ b/patches/13735.patch @@ -0,0 +1,252 @@ +From b1bb31343bb6d573761423ad649b7580d9817577 Mon Sep 17 00:00:00 2001 +From: yawkat +Date: Fri, 15 Dec 2023 15:05:21 +0100 +Subject: [PATCH 1/6] Fix exception on HTTP chunk size overflow Motivation: + +If there is an overflow in the chunk size calculation, it would throw an exception on the pipeline instead of an invalid chunk like other number format errors. In particular: + +``` +Caused by: java.lang.IllegalArgumentException: minimumReadableBytes : -1932735284 (expected: >= 0) + at io.netty.util.internal.ObjectUtil.checkPositiveOrZero(ObjectUtil.java:144) + at io.netty.buffer.AbstractByteBuf.checkReadableBytes(AbstractByteBuf.java:1428) + at io.netty.buffer.AbstractByteBuf.readRetainedSlice(AbstractByteBuf.java:887) + at io.netty.handler.codec.http.HttpObjectDecoder.decode(HttpObjectDecoder.java:480) + at io.netty.handler.codec.http.HttpServerCodec$HttpServerRequestDecoder.decode(HttpServerCodec.java:167) + at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:529) + at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:468) + ... 134 common frames omitted +``` + +Modification: + +Check for negative chunk size, and throw a NumberFormatException early. + +Result: + +This invalid input is treated like any other invalid chunk. +--- + .../handler/codec/http/HttpObjectDecoder.java | 3 +++ + .../handler/codec/http/HttpRequestDecoderTest.java | 14 ++++++++++++++ + 2 files changed, 17 insertions(+) + +diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +index 18dfde41da62..5355ae44e11a 100644 +--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java ++++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +@@ -913,6 +913,9 @@ private static int getChunkSize(byte[] hex, int start, int length) { + result *= 16; + result += digit; + } ++ if (result < 0) { ++ throw new NumberFormatException(); ++ } + return result; + } + +diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java +index 35a0def4303f..0033e8830b44 100644 +--- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java ++++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java +@@ -639,6 +639,20 @@ void headerValuesMayBeBracketedByZeroOrMoreWhitespace() throws Exception { + assertFalse(channel.finish()); + } + ++ @Test ++ public void testChunkSizeOverflow() { ++ String requestStr = "PUT /some/path HTTP/1.1\r\n" + ++ "Transfer-Encoding: chunked\r\n\r\n" + ++ "8ccccccc\r\n"; ++ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder()); ++ assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII))); ++ HttpRequest request = channel.readInbound(); ++ assertTrue(request.decoderResult().isSuccess()); ++ HttpContent c = channel.readInbound(); ++ c.release(); ++ assertFalse(channel.finish()); ++ } ++ + private static void testInvalidHeaders0(String requestStr) { + testInvalidHeaders0(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)); + } + +From 2e6a44aaec7c18b057462f213224bd81628e1afe Mon Sep 17 00:00:00 2001 +From: yawkat +Date: Fri, 15 Dec 2023 15:54:12 +0100 +Subject: [PATCH 2/6] Add message + +--- + .../java/io/netty/handler/codec/http/HttpObjectDecoder.java | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +index 5355ae44e11a..3b784d1ee3a0 100644 +--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java ++++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +@@ -914,7 +914,7 @@ private static int getChunkSize(byte[] hex, int start, int length) { + result += digit; + } + if (result < 0) { +- throw new NumberFormatException(); ++ throw new NumberFormatException("Chunk size overflow: " + result); + } + return result; + } + +From 8062eed628df05f02988f2570ebce5e0ab887856 Mon Sep 17 00:00:00 2001 +From: yawkat +Date: Tue, 19 Dec 2023 11:09:05 +0100 +Subject: [PATCH 3/6] Fix the fix + +--- + .../handler/codec/http/HttpObjectDecoder.java | 2 +- + .../handler/codec/http/HttpRequestDecoderTest.java | 14 ++++++++++++++ + 2 files changed, 15 insertions(+), 1 deletion(-) + +diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +index 3b784d1ee3a0..d79aef1a9ba8 100644 +--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java ++++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +@@ -905,7 +905,7 @@ private static int getChunkSize(byte[] hex, int start, int length) { + // empty case + throw new NumberFormatException(); + } +- return result; ++ break; + } + // non-hex char fail-fast path + throw new NumberFormatException(); +diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java +index 0033e8830b44..7f66e677cc4f 100644 +--- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java ++++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java +@@ -653,6 +653,20 @@ public void testChunkSizeOverflow() { + assertFalse(channel.finish()); + } + ++ @Test ++ public void testChunkSizeOverflow2() { ++ String requestStr = "PUT /some/path HTTP/1.1\r\n" + ++ "Transfer-Encoding: chunked\r\n\r\n" + ++ "bbbbbbbe;\n\r\n"; ++ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder()); ++ assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII))); ++ HttpRequest request = channel.readInbound(); ++ assertTrue(request.decoderResult().isSuccess()); ++ HttpContent c = channel.readInbound(); ++ c.release(); ++ assertFalse(channel.finish()); ++ } ++ + private static void testInvalidHeaders0(String requestStr) { + testInvalidHeaders0(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)); + } + +From 7ccdc7014bb839072e12925dd39e796f22b12899 Mon Sep 17 00:00:00 2001 +From: yawkat +Date: Wed, 20 Dec 2023 09:43:46 +0100 +Subject: [PATCH 4/6] move into loop + +--- + .../java/io/netty/handler/codec/http/HttpObjectDecoder.java | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +index d79aef1a9ba8..3d9d75f42d07 100644 +--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java ++++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +@@ -912,9 +912,9 @@ private static int getChunkSize(byte[] hex, int start, int length) { + } + result *= 16; + result += digit; +- } +- if (result < 0) { +- throw new NumberFormatException("Chunk size overflow: " + result); ++ if (result < 0) { ++ throw new NumberFormatException("Chunk size overflow: " + result); ++ } + } + return result; + } + +From 868a7794f17c9534f3bebeb0e40c11999043259a Mon Sep 17 00:00:00 2001 +From: yawkat +Date: Thu, 21 Dec 2023 08:58:12 +0100 +Subject: [PATCH 5/6] revert break change now that the if is inside the loop + +--- + .../java/io/netty/handler/codec/http/HttpObjectDecoder.java | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +index 3d9d75f42d07..04e615ed52da 100644 +--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java ++++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +@@ -905,7 +905,7 @@ private static int getChunkSize(byte[] hex, int start, int length) { + // empty case + throw new NumberFormatException(); + } +- break; ++ return result; + } + // non-hex char fail-fast path + throw new NumberFormatException(); + +From 1bf5af2faf3029563d1b18be303a4ec21e073355 Mon Sep 17 00:00:00 2001 +From: yawkat +Date: Thu, 21 Dec 2023 09:00:19 +0100 +Subject: [PATCH 6/6] address review + +--- + .../java/io/netty/handler/codec/http/HttpObjectDecoder.java | 4 ++-- + .../io/netty/handler/codec/http/HttpRequestDecoderTest.java | 5 +++++ + 2 files changed, 7 insertions(+), 2 deletions(-) + +diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +index 04e615ed52da..282796f465b6 100644 +--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java ++++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +@@ -903,12 +903,12 @@ private static int getChunkSize(byte[] hex, int start, int length) { + if (b == ';' || isControlOrWhitespaceAsciiChar(b)) { + if (i == 0) { + // empty case +- throw new NumberFormatException(); ++ throw new NumberFormatException("Empty chunk size"); + } + return result; + } + // non-hex char fail-fast path +- throw new NumberFormatException(); ++ throw new NumberFormatException("Invalid character in chunk size"); + } + result *= 16; + result += digit; +diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java +index 7f66e677cc4f..6a6d3589e139 100644 +--- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java ++++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java +@@ -35,6 +35,7 @@ + import static org.hamcrest.MatcherAssert.assertThat; + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertFalse; ++import static org.junit.jupiter.api.Assertions.assertInstanceOf; + import static org.junit.jupiter.api.Assertions.assertNotNull; + import static org.junit.jupiter.api.Assertions.assertNull; + import static org.junit.jupiter.api.Assertions.assertTrue; +@@ -650,6 +651,8 @@ public void testChunkSizeOverflow() { + assertTrue(request.decoderResult().isSuccess()); + HttpContent c = channel.readInbound(); + c.release(); ++ assertTrue(c.decoderResult().isFailure()); ++ assertInstanceOf(NumberFormatException.class, c.decoderResult().cause()); + assertFalse(channel.finish()); + } + +@@ -664,6 +667,8 @@ public void testChunkSizeOverflow2() { + assertTrue(request.decoderResult().isSuccess()); + HttpContent c = channel.readInbound(); + c.release(); ++ assertTrue(c.decoderResult().isFailure()); ++ assertInstanceOf(NumberFormatException.class, c.decoderResult().cause()); + assertFalse(channel.finish()); + } + diff --git a/patches/13736.patch b/patches/13736.patch new file mode 100644 index 0000000..8e24b88 --- /dev/null +++ b/patches/13736.patch @@ -0,0 +1,231 @@ +From 7d51d715e71585e71cd5a0d1ea32519e2e9ea156 Mon Sep 17 00:00:00 2001 +From: Norman Maurer +Date: Fri, 15 Dec 2023 16:45:32 +0100 +Subject: [PATCH] Default value of MAX_MESSAGES_PER_READ not used for native + DatagramChanne + +Motivation: + +Due of how we did set up the RecvByteBufAllocator we did not set the correct default value for the max message per read. + +Modifications: + +Pass the RecvByteBufAllocator in the constructor so its setup correctly. + +Result: + +Fix https://github.com/netty/netty/issues/13733 +--- + .../epoll/EpollDatagramChannelConfig.java | 3 +- + .../EpollDomainDatagramChannelConfig.java | 3 +- + .../kqueue/KQueueDatagramChannelConfig.java | 3 +- + .../KQueueDomainDatagramChannelConfig.java | 3 +- + .../epoll/EpollDatagramChannelTest.java | 7 ++++ + .../epoll/EpollDomainDatagramChannelTest.java | 36 +++++++++++++++++++ + .../kqueue/KqueueDatagramChannelTest.java | 36 +++++++++++++++++++ + .../KqueueDomainDatagramChannelTest.java | 36 +++++++++++++++++++ + 8 files changed, 119 insertions(+), 8 deletions(-) + create mode 100644 transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollDomainDatagramChannelTest.java + create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KqueueDatagramChannelTest.java + create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KqueueDomainDatagramChannelTest.java + +diff --git a/transport-classes-epoll/src/main/java/io/netty/channel/epoll/EpollDatagramChannelConfig.java b/transport-classes-epoll/src/main/java/io/netty/channel/epoll/EpollDatagramChannelConfig.java +index 850da1381508..4af835878cc9 100644 +--- a/transport-classes-epoll/src/main/java/io/netty/channel/epoll/EpollDatagramChannelConfig.java ++++ b/transport-classes-epoll/src/main/java/io/netty/channel/epoll/EpollDatagramChannelConfig.java +@@ -36,8 +36,7 @@ public final class EpollDatagramChannelConfig extends EpollChannelConfig impleme + private volatile int maxDatagramSize; + + EpollDatagramChannelConfig(EpollDatagramChannel channel) { +- super(channel); +- setRecvByteBufAllocator(new FixedRecvByteBufAllocator(2048)); ++ super(channel, new FixedRecvByteBufAllocator(2048)); + } + + @Override +diff --git a/transport-classes-epoll/src/main/java/io/netty/channel/epoll/EpollDomainDatagramChannelConfig.java b/transport-classes-epoll/src/main/java/io/netty/channel/epoll/EpollDomainDatagramChannelConfig.java +index bd06fe34e740..01380951a9d4 100644 +--- a/transport-classes-epoll/src/main/java/io/netty/channel/epoll/EpollDomainDatagramChannelConfig.java ++++ b/transport-classes-epoll/src/main/java/io/netty/channel/epoll/EpollDomainDatagramChannelConfig.java +@@ -37,8 +37,7 @@ public final class EpollDomainDatagramChannelConfig extends EpollChannelConfig i + private boolean activeOnOpen; + + EpollDomainDatagramChannelConfig(EpollDomainDatagramChannel channel) { +- super(channel); +- setRecvByteBufAllocator(new FixedRecvByteBufAllocator(2048)); ++ super(channel, new FixedRecvByteBufAllocator(2048)); + } + + @Override +diff --git a/transport-classes-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDatagramChannelConfig.java b/transport-classes-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDatagramChannelConfig.java +index d15e56cf0114..7ce2406a7f1b 100644 +--- a/transport-classes-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDatagramChannelConfig.java ++++ b/transport-classes-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDatagramChannelConfig.java +@@ -47,8 +47,7 @@ public final class KQueueDatagramChannelConfig extends KQueueChannelConfig imple + private boolean activeOnOpen; + + KQueueDatagramChannelConfig(KQueueDatagramChannel channel) { +- super(channel); +- setRecvByteBufAllocator(new FixedRecvByteBufAllocator(2048)); ++ super(channel, new FixedRecvByteBufAllocator(2048)); + } + + @Override +diff --git a/transport-classes-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDomainDatagramChannelConfig.java b/transport-classes-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDomainDatagramChannelConfig.java +index c79f097f4bf9..ee3fae8f1571 100644 +--- a/transport-classes-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDomainDatagramChannelConfig.java ++++ b/transport-classes-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDomainDatagramChannelConfig.java +@@ -38,8 +38,7 @@ public final class KQueueDomainDatagramChannelConfig + private boolean activeOnOpen; + + KQueueDomainDatagramChannelConfig(KQueueDomainDatagramChannel channel) { +- super(channel); +- setRecvByteBufAllocator(new FixedRecvByteBufAllocator(2048)); ++ super(channel, new FixedRecvByteBufAllocator(2048)); + } + + @Override +diff --git a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramChannelTest.java b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramChannelTest.java +index e83fb4ba016c..ab40ec582f96 100644 +--- a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramChannelTest.java ++++ b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollDatagramChannelTest.java +@@ -43,6 +43,13 @@ public void setUp() { + Epoll.ensureAvailability(); + } + ++ @Test ++ public void testDefaultMaxMessagePerRead() { ++ EpollDatagramChannel channel = new EpollDatagramChannel(); ++ assertEquals(16, channel.config().getMaxMessagesPerRead()); ++ channel.unsafe().closeForcibly(); ++ } ++ + @Test + public void testNotActiveNoLocalRemoteAddress() throws IOException { + checkNotActiveNoLocalRemoteAddress(new EpollDatagramChannel()); +diff --git a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollDomainDatagramChannelTest.java b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollDomainDatagramChannelTest.java +new file mode 100644 +index 000000000000..60eb72c90886 +--- /dev/null ++++ b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollDomainDatagramChannelTest.java +@@ -0,0 +1,36 @@ ++/* ++ * Copyright 2023 The Netty Project ++ * ++ * The Netty Project licenses this file to you under the Apache License, ++ * version 2.0 (the "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at: ++ * ++ * https://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT ++ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the ++ * License for the specific language governing permissions and limitations ++ * under the License. ++ */ ++package io.netty.channel.epoll; ++ ++import org.junit.jupiter.api.BeforeEach; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++ ++public class EpollDomainDatagramChannelTest { ++ ++ @BeforeEach ++ public void setUp() { ++ Epoll.ensureAvailability(); ++ } ++ ++ @Test ++ public void testDefaultMaxMessagePerRead() { ++ EpollDomainDatagramChannel channel = new EpollDomainDatagramChannel(); ++ assertEquals(16, channel.config().getMaxMessagesPerRead()); ++ channel.unsafe().closeForcibly(); ++ } ++} +diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KqueueDatagramChannelTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KqueueDatagramChannelTest.java +new file mode 100644 +index 000000000000..4b0d82243e4d +--- /dev/null ++++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KqueueDatagramChannelTest.java +@@ -0,0 +1,36 @@ ++/* ++ * Copyright 2023 The Netty Project ++ * ++ * The Netty Project licenses this file to you under the Apache License, ++ * version 2.0 (the "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at: ++ * ++ * https://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT ++ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the ++ * License for the specific language governing permissions and limitations ++ * under the License. ++ */ ++package io.netty.channel.kqueue; ++ ++import org.junit.jupiter.api.BeforeEach; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++ ++public class KqueueDatagramChannelTest { ++ ++ @BeforeEach ++ public void setUp() { ++ KQueue.ensureAvailability(); ++ } ++ ++ @Test ++ public void testDefaultMaxMessagePerRead() { ++ KQueueDatagramChannel channel = new KQueueDatagramChannel(); ++ assertEquals(16, channel.config().getMaxMessagesPerRead()); ++ channel.unsafe().closeForcibly(); ++ } ++} +diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KqueueDomainDatagramChannelTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KqueueDomainDatagramChannelTest.java +new file mode 100644 +index 000000000000..b96915078ee0 +--- /dev/null ++++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KqueueDomainDatagramChannelTest.java +@@ -0,0 +1,36 @@ ++/* ++ * Copyright 2023 The Netty Project ++ * ++ * The Netty Project licenses this file to you under the Apache License, ++ * version 2.0 (the "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at: ++ * ++ * https://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT ++ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the ++ * License for the specific language governing permissions and limitations ++ * under the License. ++ */ ++package io.netty.channel.kqueue; ++ ++import org.junit.jupiter.api.BeforeEach; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++ ++public class KqueueDomainDatagramChannelTest { ++ ++ @BeforeEach ++ public void setUp() { ++ KQueue.ensureAvailability(); ++ } ++ ++ @Test ++ public void testDefaultMaxMessagePerRead() { ++ KQueueDomainDatagramChannel channel = new KQueueDomainDatagramChannel(); ++ assertEquals(16, channel.config().getMaxMessagesPerRead()); ++ channel.unsafe().closeForcibly(); ++ } ++} diff --git a/patches/13741.patch b/patches/13741.patch new file mode 100644 index 0000000..c70d538 --- /dev/null +++ b/patches/13741.patch @@ -0,0 +1,166 @@ +From 5c55a59de4a6cd9cb8075848874e1423cfb4474d Mon Sep 17 00:00:00 2001 +From: Francesco Nigro +Date: Tue, 19 Dec 2023 14:20:22 +0100 +Subject: [PATCH] Redo fix scalability issue due to checkcast on context's + invoke operations + +Motivation: + +ChannelDuplexHandler can implements both ChannelOutboundHandler and ChannelInboundHandler causing a scalability issue due to checkcast due to https://bugs.openjdk.org/browse/JDK-8180450 +Not only: there are different classes eg Http2ConnectionHandler, which implement them transitively, by using one of the 2 existing adapters (ChannelInboundAdapter, ChanneOutboundAdapters). +The existing change at https://github.com/netty/netty/pull/12806 was fixing only the duplex cases, but others like the above one was still affected. + +Modifications: + +Replace the duplex type checks with broader inbound adapter ones, given that duplex is still based on it. +Add outbound adapters type checks in addition to duplex ones. + +Result: + +More scalable adapters-based channel handler operations. +--- + .../AbstractChannelHandlerContext.java | 40 +++++++++++++------ + 1 file changed, 28 insertions(+), 12 deletions(-) + +diff --git a/transport/src/main/java/io/netty/channel/AbstractChannelHandlerContext.java b/transport/src/main/java/io/netty/channel/AbstractChannelHandlerContext.java +index 0128e776b071..d3953f47626e 100644 +--- a/transport/src/main/java/io/netty/channel/AbstractChannelHandlerContext.java ++++ b/transport/src/main/java/io/netty/channel/AbstractChannelHandlerContext.java +@@ -170,8 +170,8 @@ private void invokeChannelRegistered() { + final DefaultChannelPipeline.HeadContext headContext = pipeline.head; + if (handler == headContext) { + headContext.channelRegistered(this); +- } else if (handler instanceof ChannelDuplexHandler) { +- ((ChannelDuplexHandler) handler).channelRegistered(this); ++ } else if (handler instanceof ChannelInboundHandlerAdapter) { ++ ((ChannelInboundHandlerAdapter) handler).channelRegistered(this); + } else { + ((ChannelInboundHandler) handler).channelRegistered(this); + } +@@ -213,8 +213,8 @@ private void invokeChannelUnregistered() { + final DefaultChannelPipeline.HeadContext headContext = pipeline.head; + if (handler == headContext) { + headContext.channelUnregistered(this); +- } else if (handler instanceof ChannelDuplexHandler) { +- ((ChannelDuplexHandler) handler).channelUnregistered(this); ++ } else if (handler instanceof ChannelInboundHandlerAdapter) { ++ ((ChannelInboundHandlerAdapter) handler).channelUnregistered(this); + } else { + ((ChannelInboundHandler) handler).channelUnregistered(this); + } +@@ -256,8 +256,8 @@ private void invokeChannelActive() { + final DefaultChannelPipeline.HeadContext headContext = pipeline.head; + if (handler == headContext) { + headContext.channelActive(this); +- } else if (handler instanceof ChannelDuplexHandler) { +- ((ChannelDuplexHandler) handler).channelActive(this); ++ } else if (handler instanceof ChannelInboundHandlerAdapter) { ++ ((ChannelInboundHandlerAdapter) handler).channelActive(this); + } else { + ((ChannelInboundHandler) handler).channelActive(this); + } +@@ -299,8 +299,8 @@ private void invokeChannelInactive() { + final DefaultChannelPipeline.HeadContext headContext = pipeline.head; + if (handler == headContext) { + headContext.channelInactive(this); +- } else if (handler instanceof ChannelDuplexHandler) { +- ((ChannelDuplexHandler) handler).channelInactive(this); ++ } else if (handler instanceof ChannelInboundHandlerAdapter) { ++ ((ChannelInboundHandlerAdapter) handler).channelInactive(this); + } else { + ((ChannelInboundHandler) handler).channelInactive(this); + } +@@ -394,8 +394,8 @@ private void invokeUserEventTriggered(Object event) { + final DefaultChannelPipeline.HeadContext headContext = pipeline.head; + if (handler == headContext) { + headContext.userEventTriggered(this, event); +- } else if (handler instanceof ChannelDuplexHandler) { +- ((ChannelDuplexHandler) handler).userEventTriggered(this, event); ++ } else if (handler instanceof ChannelInboundHandlerAdapter) { ++ ((ChannelInboundHandlerAdapter) handler).userEventTriggered(this, event); + } else { + ((ChannelInboundHandler) handler).userEventTriggered(this, event); + } +@@ -522,8 +522,8 @@ private void invokeChannelWritabilityChanged() { + final DefaultChannelPipeline.HeadContext headContext = pipeline.head; + if (handler == headContext) { + headContext.channelWritabilityChanged(this); +- } else if (handler instanceof ChannelDuplexHandler) { +- ((ChannelDuplexHandler) handler).channelWritabilityChanged(this); ++ } else if (handler instanceof ChannelInboundHandlerAdapter) { ++ ((ChannelInboundHandlerAdapter) handler).channelWritabilityChanged(this); + } else { + ((ChannelInboundHandler) handler).channelWritabilityChanged(this); + } +@@ -600,6 +600,8 @@ private void invokeBind(SocketAddress localAddress, ChannelPromise promise) { + headContext.bind(this, localAddress, promise); + } else if (handler instanceof ChannelDuplexHandler) { + ((ChannelDuplexHandler) handler).bind(this, localAddress, promise); ++ } else if (handler instanceof ChannelOutboundHandlerAdapter) { ++ ((ChannelOutboundHandlerAdapter) handler).bind(this, localAddress, promise); + } else { + ((ChannelOutboundHandler) handler).bind(this, localAddress, promise); + } +@@ -653,6 +655,8 @@ private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddre + headContext.connect(this, remoteAddress, localAddress, promise); + } else if (handler instanceof ChannelDuplexHandler) { + ((ChannelDuplexHandler) handler).connect(this, remoteAddress, localAddress, promise); ++ } else if (handler instanceof ChannelOutboundHandlerAdapter) { ++ ((ChannelOutboundHandlerAdapter) handler).connect(this, remoteAddress, localAddress, promise); + } else { + ((ChannelOutboundHandler) handler).connect(this, remoteAddress, localAddress, promise); + } +@@ -703,6 +707,8 @@ private void invokeDisconnect(ChannelPromise promise) { + headContext.disconnect(this, promise); + } else if (handler instanceof ChannelDuplexHandler) { + ((ChannelDuplexHandler) handler).disconnect(this, promise); ++ } else if (handler instanceof ChannelOutboundHandlerAdapter) { ++ ((ChannelOutboundHandlerAdapter) handler).disconnect(this, promise); + } else { + ((ChannelOutboundHandler) handler).disconnect(this, promise); + } +@@ -749,6 +755,8 @@ private void invokeClose(ChannelPromise promise) { + headContext.close(this, promise); + } else if (handler instanceof ChannelDuplexHandler) { + ((ChannelDuplexHandler) handler).close(this, promise); ++ } else if (handler instanceof ChannelOutboundHandlerAdapter) { ++ ((ChannelOutboundHandlerAdapter) handler).close(this, promise); + } else { + ((ChannelOutboundHandler) handler).close(this, promise); + } +@@ -795,6 +803,8 @@ private void invokeDeregister(ChannelPromise promise) { + headContext.deregister(this, promise); + } else if (handler instanceof ChannelDuplexHandler) { + ((ChannelDuplexHandler) handler).deregister(this, promise); ++ } else if (handler instanceof ChannelOutboundHandlerAdapter) { ++ ((ChannelOutboundHandlerAdapter) handler).deregister(this, promise); + } else { + ((ChannelOutboundHandler) handler).deregister(this, promise); + } +@@ -835,6 +845,8 @@ private void invokeRead() { + headContext.read(this); + } else if (handler instanceof ChannelDuplexHandler) { + ((ChannelDuplexHandler) handler).read(this); ++ } else if (handler instanceof ChannelOutboundHandlerAdapter) { ++ ((ChannelOutboundHandlerAdapter) handler).read(this); + } else { + ((ChannelOutboundHandler) handler).read(this); + } +@@ -877,6 +889,8 @@ private void invokeWrite0(Object msg, ChannelPromise promise) { + headContext.write(this, msg, promise); + } else if (handler instanceof ChannelDuplexHandler) { + ((ChannelDuplexHandler) handler).write(this, msg, promise); ++ } else if (handler instanceof ChannelOutboundHandlerAdapter) { ++ ((ChannelOutboundHandlerAdapter) handler).write(this, msg, promise); + } else { + ((ChannelOutboundHandler) handler).write(this, msg, promise); + } +@@ -921,6 +935,8 @@ private void invokeFlush0() { + headContext.flush(this); + } else if (handler instanceof ChannelDuplexHandler) { + ((ChannelDuplexHandler) handler).flush(this); ++ } else if (handler instanceof ChannelOutboundHandlerAdapter) { ++ ((ChannelOutboundHandlerAdapter) handler).flush(this); + } else { + ((ChannelOutboundHandler) handler).flush(this); + } diff --git a/patches/13757.patch b/patches/13757.patch new file mode 100644 index 0000000..279d8c3 --- /dev/null +++ b/patches/13757.patch @@ -0,0 +1,1313 @@ +From 0b6ce4c1d2106815a80f48d7903160009c09c03a Mon Sep 17 00:00:00 2001 +From: Norman Maurer +Date: Fri, 29 Dec 2023 17:03:56 +0100 +Subject: [PATCH 1/4] Retry the query via TCP if a query failed because of a + timeout when using UDP + +Motivation: + +We should retry the query via TCP if the query failed because of a timeout when using UDP. + +Modifications: + +- Move all the retry code for TCP into DnsQueryContext so we can reuse the same code for handling truncation and retry. +- Retry on timeout if possible +- Add unit tests + +Result: + +More robust resolver +--- + .../resolver/dns/DatagramDnsQueryContext.java | 7 +- + .../netty/resolver/dns/DnsNameResolver.java | 223 ++------------ + .../netty/resolver/dns/DnsQueryContext.java | 278 ++++++++++++++++-- + .../resolver/dns/TcpDnsQueryContext.java | 4 +- + .../generated/handlers/reflect-config.json | 15 +- + .../resolver/dns/DnsNameResolverTest.java | 155 ++++++++-- + 6 files changed, 428 insertions(+), 254 deletions(-) + +diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DatagramDnsQueryContext.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DatagramDnsQueryContext.java +index 4cea712833e2..4c5487069079 100644 +--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DatagramDnsQueryContext.java ++++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DatagramDnsQueryContext.java +@@ -15,6 +15,7 @@ + */ + package io.netty.resolver.dns; + ++import io.netty.bootstrap.Bootstrap; + import io.netty.channel.AddressedEnvelope; + import io.netty.channel.Channel; + import io.netty.handler.codec.dns.DatagramDnsQuery; +@@ -33,10 +34,12 @@ final class DatagramDnsQueryContext extends DnsQueryContext { + InetSocketAddress nameServerAddr, + DnsQueryContextManager queryContextManager, + int maxPayLoadSize, boolean recursionDesired, ++ long queryTimeoutMillis, + DnsQuestion question, DnsRecord[] additionals, +- Promise> promise) { ++ Promise> promise, ++ Bootstrap socketBootstrap) { + super(channel, channelReadyFuture, nameServerAddr, queryContextManager, maxPayLoadSize, recursionDesired, +- question, additionals, promise); ++ queryTimeoutMillis, question, additionals, promise, socketBootstrap); + } + + @Override +diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java +index 535b87cee39f..6939177e540c 100644 +--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java ++++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java +@@ -23,6 +23,8 @@ + import io.netty.channel.ChannelFactory; + import io.netty.channel.ChannelFuture; + import io.netty.channel.ChannelFutureListener; ++import io.netty.channel.ChannelHandler; ++import io.netty.channel.ChannelHandlerAdapter; + import io.netty.channel.ChannelHandlerContext; + import io.netty.channel.ChannelInboundHandlerAdapter; + import io.netty.channel.ChannelInitializer; +@@ -43,8 +45,6 @@ + import io.netty.handler.codec.dns.DnsRecord; + import io.netty.handler.codec.dns.DnsRecordType; + import io.netty.handler.codec.dns.DnsResponse; +-import io.netty.handler.codec.dns.TcpDnsQueryEncoder; +-import io.netty.handler.codec.dns.TcpDnsResponseDecoder; + import io.netty.resolver.DefaultHostsFileEntriesResolver; + import io.netty.resolver.HostsFileEntries; + import io.netty.resolver.HostsFileEntriesResolver; +@@ -121,6 +121,13 @@ public class DnsNameResolver extends InetNameResolver { + private static final InternetProtocolFamily[] IPV6_PREFERRED_RESOLVED_PROTOCOL_FAMILIES = + {InternetProtocolFamily.IPv6, InternetProtocolFamily.IPv4}; + ++ private static final ChannelHandler NOOP_HANDLER = new ChannelHandlerAdapter() { ++ @Override ++ public boolean isSharable() { ++ return true; ++ } ++ }; ++ + static final ResolvedAddressTypes DEFAULT_RESOLVE_ADDRESS_TYPES; + static final String[] DEFAULT_SEARCH_DOMAINS; + private static final UnixResolverOptions DEFAULT_OPTIONS; +@@ -227,7 +234,6 @@ protected DnsResponse decodeResponse(ChannelHandlerContext ctx, DatagramPacket p + } + }; + private static final DatagramDnsQueryEncoder DATAGRAM_ENCODER = new DatagramDnsQueryEncoder(); +- private static final TcpDnsQueryEncoder TCP_ENCODER = new TcpDnsQueryEncoder(); + + private final Promise channelReadyPromise; + private final Channel ch; +@@ -465,7 +471,12 @@ public DnsNameResolver( + .group(executor()) + .channelFactory(socketChannelFactory) + .attr(DNS_PIPELINE_ATTRIBUTE, Boolean.TRUE) +- .handler(TCP_ENCODER); ++ .handler(NOOP_HANDLER); ++ if (queryTimeoutMillis > 0) { ++ // Set the connect timeout to the same as queryTimeout as otherwise it might take a long ++ // time to fail the original query if the connect times out. ++ socketBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) queryTimeoutMillis); ++ } + } + switch (this.resolvedAddressTypes) { + case IPV4_ONLY: +@@ -1349,8 +1360,9 @@ final Future> query0( + final int payloadSize = isOptResourceEnabled() ? maxPayloadSize() : 0; + try { + DnsQueryContext queryContext = new DatagramDnsQueryContext(ch, channelReadyPromise, nameServerAddr, +- queryContextManager, payloadSize, isRecursionDesired(), question, additionals, castPromise); +- ChannelFuture future = queryContext.writeQuery(queryTimeoutMillis(), flush); ++ queryContextManager, payloadSize, isRecursionDesired(), queryTimeoutMillis(), question, additionals, ++ castPromise, socketBootstrap); ++ ChannelFuture future = queryContext.writeQuery(flush); + queryLifecycleObserver.queryWritten(nameServerAddr, future); + return castPromise; + } catch (Exception e) { +@@ -1395,94 +1407,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { + return; + } + +- // Check if the response was truncated and if we can fallback to TCP to retry. +- if (!res.isTruncated() || socketBootstrap == null) { +- qCtx.finishSuccess(res); +- return; +- } +- +- socketBootstrap.connect(res.sender()).addListener(new ChannelFutureListener() { +- @Override +- public void operationComplete(ChannelFuture future) { +- if (!future.isSuccess()) { +- logger.debug("{} Unable to fallback to TCP [{}: {}]", +- ch, queryId, res.sender(), future.cause()); +- +- // TCP fallback failed, just use the truncated response. +- qCtx.finishSuccess(res); +- return; +- } +- final Channel tcpCh = future.channel(); +- +- Promise> promise = +- tcpCh.eventLoop().newPromise(); +- final int payloadSize = isOptResourceEnabled() ? maxPayloadSize() : 0; +- final TcpDnsQueryContext tcpCtx = new TcpDnsQueryContext(tcpCh, channelReadyPromise, +- (InetSocketAddress) tcpCh.remoteAddress(), queryContextManager, payloadSize, +- isRecursionDesired(), qCtx.question(), EMPTY_ADDITIONALS, promise); +- +- tcpCh.pipeline().addLast(new TcpDnsResponseDecoder()); +- tcpCh.pipeline().addLast(new ChannelInboundHandlerAdapter() { +- @Override +- public void channelRead(ChannelHandlerContext ctx, Object msg) { +- Channel tcpCh = ctx.channel(); +- DnsResponse response = (DnsResponse) msg; +- int queryId = response.id(); +- +- if (logger.isDebugEnabled()) { +- logger.debug("{} RECEIVED: TCP [{}: {}], {}", tcpCh, queryId, +- tcpCh.remoteAddress(), response); +- } +- +- DnsQueryContext foundCtx = queryContextManager.get(res.sender(), queryId); +- if (foundCtx != null && foundCtx.isDone()) { +- logger.debug("{} Received a DNS response for a query that was timed out or cancelled " + +- ": TCP [{}: {}]", tcpCh, queryId, res.sender()); +- response.release(); +- } else if (foundCtx == tcpCtx) { +- tcpCtx.finishSuccess(new AddressedEnvelopeAdapter( +- (InetSocketAddress) ctx.channel().remoteAddress(), +- (InetSocketAddress) ctx.channel().localAddress(), +- response)); +- } else { +- response.release(); +- tcpCtx.finishFailure("Received TCP DNS response with unexpected ID", null, false); +- if (logger.isDebugEnabled()) { +- logger.debug("{} Received a DNS response with an unexpected ID: TCP [{}: {}]", +- tcpCh, queryId, tcpCh.remoteAddress()); +- } +- } +- } +- +- @Override +- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { +- if (tcpCtx.finishFailure( +- "TCP fallback error", cause, false) && logger.isDebugEnabled()) { +- logger.debug("{} Error during processing response: TCP [{}: {}]", +- ctx.channel(), queryId, +- ctx.channel().remoteAddress(), cause); +- } +- } +- }); +- +- promise.addListener( +- new FutureListener>() { +- @Override +- public void operationComplete( +- Future> future) { +- if (future.isSuccess()) { +- qCtx.finishSuccess(future.getNow()); +- res.release(); +- } else { +- // TCP fallback failed, just use the truncated response. +- qCtx.finishSuccess(res); +- } +- tcpCh.close(); +- } +- }); +- tcpCtx.writeQuery(queryTimeoutMillis(), true); +- } +- }); ++ // The context will handle truncation itself. ++ qCtx.finishSuccess(res, res.isTruncated()); + } + + @Override +@@ -1500,113 +1426,4 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + } + } + } +- +- private static final class AddressedEnvelopeAdapter implements AddressedEnvelope { +- private final InetSocketAddress sender; +- private final InetSocketAddress recipient; +- private final DnsResponse response; +- +- AddressedEnvelopeAdapter(InetSocketAddress sender, InetSocketAddress recipient, DnsResponse response) { +- this.sender = sender; +- this.recipient = recipient; +- this.response = response; +- } +- +- @Override +- public DnsResponse content() { +- return response; +- } +- +- @Override +- public InetSocketAddress sender() { +- return sender; +- } +- +- @Override +- public InetSocketAddress recipient() { +- return recipient; +- } +- +- @Override +- public AddressedEnvelope retain() { +- response.retain(); +- return this; +- } +- +- @Override +- public AddressedEnvelope retain(int increment) { +- response.retain(increment); +- return this; +- } +- +- @Override +- public AddressedEnvelope touch() { +- response.touch(); +- return this; +- } +- +- @Override +- public AddressedEnvelope touch(Object hint) { +- response.touch(hint); +- return this; +- } +- +- @Override +- public int refCnt() { +- return response.refCnt(); +- } +- +- @Override +- public boolean release() { +- return response.release(); +- } +- +- @Override +- public boolean release(int decrement) { +- return response.release(decrement); +- } +- +- @Override +- public boolean equals(Object obj) { +- if (this == obj) { +- return true; +- } +- +- if (!(obj instanceof AddressedEnvelope)) { +- return false; +- } +- +- @SuppressWarnings("unchecked") +- final AddressedEnvelope that = (AddressedEnvelope) obj; +- if (sender() == null) { +- if (that.sender() != null) { +- return false; +- } +- } else if (!sender().equals(that.sender())) { +- return false; +- } +- +- if (recipient() == null) { +- if (that.recipient() != null) { +- return false; +- } +- } else if (!recipient().equals(that.recipient())) { +- return false; +- } +- +- return response.equals(obj); +- } +- +- @Override +- public int hashCode() { +- int hashCode = response.hashCode(); +- if (sender() != null) { +- hashCode = hashCode * 31 + sender().hashCode(); +- } +- if (recipient() != null) { +- hashCode = hashCode * 31 + recipient().hashCode(); +- } +- return hashCode; +- } +- } + } +diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java +index e3db5f807f6d..1741e25dd543 100644 +--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java ++++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java +@@ -15,10 +15,13 @@ + */ + package io.netty.resolver.dns; + ++import io.netty.bootstrap.Bootstrap; + import io.netty.channel.AddressedEnvelope; + import io.netty.channel.Channel; + import io.netty.channel.ChannelFuture; + import io.netty.channel.ChannelFutureListener; ++import io.netty.channel.ChannelHandlerContext; ++import io.netty.channel.ChannelInboundHandlerAdapter; + import io.netty.channel.ChannelPromise; + import io.netty.handler.codec.dns.AbstractDnsOptPseudoRrRecord; + import io.netty.handler.codec.dns.DnsQuery; +@@ -27,16 +30,20 @@ + import io.netty.handler.codec.dns.DnsRecordType; + import io.netty.handler.codec.dns.DnsResponse; + import io.netty.handler.codec.dns.DnsSection; ++import io.netty.handler.codec.dns.TcpDnsQueryEncoder; ++import io.netty.handler.codec.dns.TcpDnsResponseDecoder; + import io.netty.util.ReferenceCountUtil; + import io.netty.util.concurrent.Future; + import io.netty.util.concurrent.FutureListener; + import io.netty.util.concurrent.GenericFutureListener; + import io.netty.util.concurrent.Promise; + import io.netty.util.internal.SystemPropertyUtil; ++import io.netty.util.internal.ThrowableUtil; + import io.netty.util.internal.logging.InternalLogger; + import io.netty.util.internal.logging.InternalLoggerFactory; + + import java.net.InetSocketAddress; ++import java.net.SocketAddress; + import java.util.concurrent.CancellationException; + import java.util.concurrent.TimeUnit; + +@@ -53,6 +60,8 @@ abstract class DnsQueryContext { + logger.debug("-Dio.netty.resolver.dns.idReuseOnTimeoutDelayMillis: {}", ID_REUSE_ON_TIMEOUT_DELAY_MILLIS); + } + ++ private static final TcpDnsQueryEncoder TCP_ENCODER = new TcpDnsQueryEncoder(); ++ + private final Future channelReadyFuture; + private final Channel channel; + private final InetSocketAddress nameServerAddr; +@@ -64,6 +73,10 @@ abstract class DnsQueryContext { + private final DnsRecord optResource; + + private final boolean recursionDesired; ++ ++ private final Bootstrap socketBootstrap; ++ private final long queryTimeoutMillis; ++ + private volatile Future timeoutFuture; + + private int id = -1; +@@ -74,9 +87,10 @@ abstract class DnsQueryContext { + DnsQueryContextManager queryContextManager, + int maxPayLoadSize, + boolean recursionDesired, ++ long queryTimeoutMillis, + DnsQuestion question, + DnsRecord[] additionals, +- Promise> promise) { ++ Promise> promise, Bootstrap socketBootstrap) { + this.channel = checkNotNull(channel, "channel"); + this.queryContextManager = checkNotNull(queryContextManager, "queryContextManager"); + this.channelReadyFuture = checkNotNull(channelReadyFuture, "channelReadyFuture"); +@@ -85,6 +99,8 @@ abstract class DnsQueryContext { + this.additionals = checkNotNull(additionals, "additionals"); + this.promise = checkNotNull(promise, "promise"); + this.recursionDesired = recursionDesired; ++ this.queryTimeoutMillis = queryTimeoutMillis; ++ this.socketBootstrap = socketBootstrap; + + if (maxPayLoadSize > 0 && + // Only add the extra OPT record if there is not already one. This is required as only one is allowed +@@ -147,12 +163,10 @@ final DnsQuestion question() { + /** + * Write the query and return the {@link ChannelFuture} that is completed once the write completes. + * +- * @param queryTimeoutMillis the timeout after which the query is considered timeout and the original +- * {@link Promise} will be failed. + * @param flush {@code true} if {@link Channel#flush()} should be called as well. + * @return the {@link ChannelFuture} that is notified once once the write completes. + */ +- final ChannelFuture writeQuery(long queryTimeoutMillis, boolean flush) { ++ final ChannelFuture writeQuery(boolean flush) { + assert id == -1 : this.getClass().getSimpleName() + ".writeQuery(...) can only be executed once."; + id = queryContextManager.add(nameServerAddr, this); + +@@ -205,7 +219,7 @@ public void run() { + channel, protocol(), id, nameServerAddr, question); + } + +- return sendQuery(nameServerAddr, query, queryTimeoutMillis, flush); ++ return sendQuery(query, flush); + } + + private void removeFromContextManager(InetSocketAddress nameServerAddr) { +@@ -214,11 +228,10 @@ private void removeFromContextManager(InetSocketAddress nameServerAddr) { + assert self == this : "Removed DnsQueryContext is not the correct instance"; + } + +- private ChannelFuture sendQuery(final InetSocketAddress nameServerAddr, final DnsQuery query, +- final long queryTimeoutMillis, final boolean flush) { ++ private ChannelFuture sendQuery(final DnsQuery query, final boolean flush) { + final ChannelPromise writePromise = channel.newPromise(); + if (channelReadyFuture.isSuccess()) { +- writeQuery(nameServerAddr, query, queryTimeoutMillis, flush, writePromise); ++ writeQuery(query, flush, writePromise); + } else { + Throwable cause = channelReadyFuture.cause(); + if (cause != null) { +@@ -233,7 +246,7 @@ public void operationComplete(Future future) { + // If the query is done in a late fashion (as the channel was not ready yet) we always flush + // to ensure we did not race with a previous flush() that was done when the Channel was not + // ready yet. +- writeQuery(nameServerAddr, query, queryTimeoutMillis, true, writePromise); ++ writeQuery(query, true, writePromise); + } else { + Throwable cause = future.cause(); + failQuery(query, cause, writePromise); +@@ -254,7 +267,7 @@ private void failQuery(DnsQuery query, Throwable cause, ChannelPromise writeProm + } + } + +- private void writeQuery(final InetSocketAddress nameServerAddr, final DnsQuery query, final long queryTimeoutMillis, ++ private void writeQuery(final DnsQuery query, + final boolean flush, ChannelPromise promise) { + final ChannelFuture writeFuture = flush ? channel.writeAndFlush(query, promise) : + channel.write(query, promise); +@@ -298,18 +311,21 @@ public void run() { + * Notifies the original {@link Promise} that the response for the query was received. + * This method takes ownership of passed {@link AddressedEnvelope}. + */ +- void finishSuccess(AddressedEnvelope envelope) { +- final DnsResponse res = envelope.content(); +- if (res.count(DnsSection.QUESTION) != 1) { +- logger.warn("{} Received a DNS response with invalid number of questions. Expected: 1, found: {}", +- channel, envelope); +- } else if (!question().equals(res.recordAt(DnsSection.QUESTION))) { +- logger.warn("{} Received a mismatching DNS response. Expected: [{}], found: {}", +- channel, question(), envelope); +- } else if (trySuccess(envelope)) { +- return; // Ownership transferred, don't release ++ void finishSuccess(AddressedEnvelope envelope, boolean truncated) { ++ // Check if the response was not truncated or if a fallback to TCP is possible. ++ if (!truncated || !retryWithTCP(envelope)) { ++ final DnsResponse res = envelope.content(); ++ if (res.count(DnsSection.QUESTION) != 1) { ++ logger.warn("{} Received a DNS response with invalid number of questions. Expected: 1, found: {}", ++ channel, envelope); ++ } else if (!question().equals(res.recordAt(DnsSection.QUESTION))) { ++ logger.warn("{} Received a mismatching DNS response. Expected: [{}], found: {}", ++ channel, question(), envelope); ++ } else if (trySuccess(envelope)) { ++ return; // Ownership transferred, don't release ++ } ++ envelope.release(); + } +- envelope.release(); + } + + @SuppressWarnings("unchecked") +@@ -342,9 +358,229 @@ final boolean finishFailure(String message, Throwable cause, boolean timeout) { + // This was caused by a timeout so use DnsNameResolverTimeoutException to allow the user to + // handle it special (like retry the query). + e = new DnsNameResolverTimeoutException(nameServerAddr, question, buf.toString()); ++ if (retryWithTCP(e)) { ++ // We did successfully retry with TCP. ++ return false; ++ } + } else { + e = new DnsNameResolverException(nameServerAddr, question, buf.toString(), cause); + } + return promise.tryFailure(e); + } ++ ++ /** ++ * Retry the original query with TCP if possible. ++ * ++ * @param originalResult the result of the original {@link DnsQueryContext}. ++ * @return {@code true} if retry via TCP is supported and so the ownership of ++ * {@code originalResult} was transferred, {@code false} otherwise. ++ */ ++ private boolean retryWithTCP(final Object originalResult) { ++ if (socketBootstrap == null) { ++ return false; ++ } ++ ++ socketBootstrap.connect(nameServerAddr).addListener(new ChannelFutureListener() { ++ @Override ++ public void operationComplete(ChannelFuture future) { ++ if (!future.isSuccess()) { ++ logger.debug("{} Unable to fallback to TCP [{}: {}]", ++ future.channel(), id, nameServerAddr, future.cause()); ++ ++ // TCP fallback failed, just use the truncated response or error. ++ finishOriginal(originalResult, future); ++ return; ++ } ++ final Channel tcpCh = future.channel(); ++ Promise> promise = ++ tcpCh.eventLoop().newPromise(); ++ final TcpDnsQueryContext tcpCtx = new TcpDnsQueryContext(tcpCh, channelReadyFuture, ++ (InetSocketAddress) tcpCh.remoteAddress(), queryContextManager, 0, ++ recursionDesired, queryTimeoutMillis, question(), additionals, promise); ++ tcpCh.pipeline().addLast(TCP_ENCODER); ++ tcpCh.pipeline().addLast(new TcpDnsResponseDecoder()); ++ tcpCh.pipeline().addLast(new ChannelInboundHandlerAdapter() { ++ @Override ++ public void channelRead(ChannelHandlerContext ctx, Object msg) { ++ Channel tcpCh = ctx.channel(); ++ DnsResponse response = (DnsResponse) msg; ++ int queryId = response.id(); ++ ++ if (logger.isDebugEnabled()) { ++ logger.debug("{} RECEIVED: TCP [{}: {}], {}", tcpCh, queryId, ++ tcpCh.remoteAddress(), response); ++ } ++ ++ DnsQueryContext foundCtx = queryContextManager.get(nameServerAddr, queryId); ++ if (foundCtx != null && foundCtx.isDone()) { ++ logger.debug("{} Received a DNS response for a query that was timed out or cancelled " + ++ ": TCP [{}: {}]", tcpCh, queryId, nameServerAddr); ++ response.release(); ++ } else if (foundCtx == tcpCtx) { ++ tcpCtx.finishSuccess(new AddressedEnvelopeAdapter( ++ (InetSocketAddress) ctx.channel().remoteAddress(), ++ (InetSocketAddress) ctx.channel().localAddress(), ++ response), false); ++ } else { ++ response.release(); ++ tcpCtx.finishFailure("Received TCP DNS response with unexpected ID", null, false); ++ if (logger.isDebugEnabled()) { ++ logger.debug("{} Received a DNS response with an unexpected ID: TCP [{}: {}]", ++ tcpCh, queryId, tcpCh.remoteAddress()); ++ } ++ } ++ } ++ ++ @Override ++ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { ++ if (tcpCtx.finishFailure( ++ "TCP fallback error", cause, false) && logger.isDebugEnabled()) { ++ logger.debug("{} Error during processing response: TCP [{}: {}]", ++ ctx.channel(), id, ++ ctx.channel().remoteAddress(), cause); ++ } ++ } ++ }); ++ ++ promise.addListener( ++ new FutureListener>() { ++ @Override ++ public void operationComplete( ++ Future> future) { ++ if (future.isSuccess()) { ++ finishSuccess(future.getNow(), false); ++ // Release the original result. ++ ReferenceCountUtil.release(originalResult); ++ } else { ++ // TCP fallback failed, just use the truncated response or error. ++ finishOriginal(originalResult, future); ++ } ++ tcpCh.close(); ++ } ++ }); ++ tcpCtx.writeQuery(true); ++ } ++ }); ++ return true; ++ } ++ ++ @SuppressWarnings("unchecked") ++ private void finishOriginal(Object originalResult, Future future) { ++ if (originalResult instanceof Throwable) { ++ Throwable error = (Throwable) originalResult; ++ ThrowableUtil.addSuppressed(error, future.cause()); ++ promise.tryFailure(error); ++ } else { ++ finishSuccess((AddressedEnvelope) originalResult, false); ++ } ++ } ++ ++ private static final class AddressedEnvelopeAdapter implements AddressedEnvelope { ++ private final InetSocketAddress sender; ++ private final InetSocketAddress recipient; ++ private final DnsResponse response; ++ ++ AddressedEnvelopeAdapter(InetSocketAddress sender, InetSocketAddress recipient, DnsResponse response) { ++ this.sender = sender; ++ this.recipient = recipient; ++ this.response = response; ++ } ++ ++ @Override ++ public DnsResponse content() { ++ return response; ++ } ++ ++ @Override ++ public InetSocketAddress sender() { ++ return sender; ++ } ++ ++ @Override ++ public InetSocketAddress recipient() { ++ return recipient; ++ } ++ ++ @Override ++ public AddressedEnvelope retain() { ++ response.retain(); ++ return this; ++ } ++ ++ @Override ++ public AddressedEnvelope retain(int increment) { ++ response.retain(increment); ++ return this; ++ } ++ ++ @Override ++ public AddressedEnvelope touch() { ++ response.touch(); ++ return this; ++ } ++ ++ @Override ++ public AddressedEnvelope touch(Object hint) { ++ response.touch(hint); ++ return this; ++ } ++ ++ @Override ++ public int refCnt() { ++ return response.refCnt(); ++ } ++ ++ @Override ++ public boolean release() { ++ return response.release(); ++ } ++ ++ @Override ++ public boolean release(int decrement) { ++ return response.release(decrement); ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ ++ if (!(obj instanceof AddressedEnvelope)) { ++ return false; ++ } ++ ++ @SuppressWarnings("unchecked") ++ final AddressedEnvelope that = (AddressedEnvelope) obj; ++ if (sender() == null) { ++ if (that.sender() != null) { ++ return false; ++ } ++ } else if (!sender().equals(that.sender())) { ++ return false; ++ } ++ ++ if (recipient() == null) { ++ if (that.recipient() != null) { ++ return false; ++ } ++ } else if (!recipient().equals(that.recipient())) { ++ return false; ++ } ++ ++ return response.equals(obj); ++ } ++ ++ @Override ++ public int hashCode() { ++ int hashCode = response.hashCode(); ++ if (sender() != null) { ++ hashCode = hashCode * 31 + sender().hashCode(); ++ } ++ if (recipient() != null) { ++ hashCode = hashCode * 31 + recipient().hashCode(); ++ } ++ return hashCode; ++ } ++ } + } +diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/TcpDnsQueryContext.java b/resolver-dns/src/main/java/io/netty/resolver/dns/TcpDnsQueryContext.java +index 8f25ab8664e7..2111b67aad9c 100644 +--- a/resolver-dns/src/main/java/io/netty/resolver/dns/TcpDnsQueryContext.java ++++ b/resolver-dns/src/main/java/io/netty/resolver/dns/TcpDnsQueryContext.java +@@ -33,10 +33,12 @@ final class TcpDnsQueryContext extends DnsQueryContext { + InetSocketAddress nameServerAddr, + DnsQueryContextManager queryContextManager, + int maxPayLoadSize, boolean recursionDesired, ++ long queryTimeoutMillis, + DnsQuestion question, DnsRecord[] additionals, + Promise> promise) { + super(channel, channelReadyFuture, nameServerAddr, queryContextManager, maxPayLoadSize, recursionDesired, +- question, additionals, promise); ++ // No retry via TCP. ++ queryTimeoutMillis, question, additionals, promise, null); + } + + @Override +diff --git a/resolver-dns/src/main/resources/META-INF/native-image/io.netty/netty-resolver-dns/generated/handlers/reflect-config.json b/resolver-dns/src/main/resources/META-INF/native-image/io.netty/netty-resolver-dns/generated/handlers/reflect-config.json +index 5960b0047047..68508d9de7b0 100644 +--- a/resolver-dns/src/main/resources/META-INF/native-image/io.netty/netty-resolver-dns/generated/handlers/reflect-config.json ++++ b/resolver-dns/src/main/resources/META-INF/native-image/io.netty/netty-resolver-dns/generated/handlers/reflect-config.json +@@ -7,9 +7,16 @@ + "queryAllPublicMethods": true + }, + { +- "name": "io.netty.resolver.dns.DnsNameResolver$3", ++ "name": "io.netty.resolver.dns.DnsNameResolver$2", + "condition": { +- "typeReachable": "io.netty.resolver.dns.DnsNameResolver$3" ++ "typeReachable": "io.netty.resolver.dns.DnsNameResolver$2" ++ }, ++ "queryAllPublicMethods": true ++ }, ++ { ++ "name": "io.netty.resolver.dns.DnsNameResolver$4", ++ "condition": { ++ "typeReachable": "io.netty.resolver.dns.DnsNameResolver$4" + }, + "queryAllPublicMethods": true + }, +@@ -21,9 +28,9 @@ + "queryAllPublicMethods": true + }, + { +- "name": "io.netty.resolver.dns.DnsNameResolver$DnsResponseHandler$1$1", ++ "name": "io.netty.resolver.dns.DnsQueryContext$6$1", + "condition": { +- "typeReachable": "io.netty.resolver.dns.DnsNameResolver$DnsResponseHandler$1$1" ++ "typeReachable": "io.netty.resolver.dns.DnsQueryContext$6$1" + }, + "queryAllPublicMethods": true + } +diff --git a/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java b/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java +index 4977981a53b3..30277cb9a6c7 100644 +--- a/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java ++++ b/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java +@@ -3293,30 +3293,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (tcpFallback) { + // If we are configured to use TCP as a fallback lets replay the dns message over TCP + Socket socket = serverSocket.accept(); ++ responseViaSocket(socket, messageRef.get()); + +- InputStream in = socket.getInputStream(); +- assertTrue((in.read() << 8 | (in.read() & 0xff)) > 2); // skip length field +- int txnId = in.read() << 8 | (in.read() & 0xff); +- +- IoBuffer ioBuffer = IoBuffer.allocate(1024); +- // Must replace the transactionId with the one from the TCP request +- DnsMessageModifier modifier = modifierFrom(messageRef.get()); +- modifier.setTransactionId(txnId); +- new DnsMessageEncoder().encode(ioBuffer, modifier.getDnsMessage()); +- ioBuffer.flip(); +- +- ByteBuffer lenBuffer = ByteBuffer.allocate(2); +- lenBuffer.putShort((short) ioBuffer.remaining()); +- lenBuffer.flip(); +- +- while (lenBuffer.hasRemaining()) { +- socket.getOutputStream().write(lenBuffer.get()); +- } +- +- while (ioBuffer.hasRemaining()) { +- socket.getOutputStream().write(ioBuffer.get()); +- } +- socket.getOutputStream().flush(); + // Let's wait until we received the envelope before closing the socket. + envelopeFuture.syncUninterruptibly(); + +@@ -3352,6 +3330,137 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { + } + } + ++ private static void responseViaSocket(Socket socket, DnsMessage message) throws IOException { ++ InputStream in = socket.getInputStream(); ++ assertTrue((in.read() << 8 | (in.read() & 0xff)) > 2); // skip length field ++ int txnId = in.read() << 8 | (in.read() & 0xff); ++ ++ IoBuffer ioBuffer = IoBuffer.allocate(1024); ++ // Must replace the transactionId with the one from the TCP request ++ DnsMessageModifier modifier = modifierFrom(message); ++ modifier.setTransactionId(txnId); ++ new DnsMessageEncoder().encode(ioBuffer, modifier.getDnsMessage()); ++ ioBuffer.flip(); ++ ++ ByteBuffer lenBuffer = ByteBuffer.allocate(2); ++ lenBuffer.putShort((short) ioBuffer.remaining()); ++ lenBuffer.flip(); ++ ++ while (lenBuffer.hasRemaining()) { ++ socket.getOutputStream().write(lenBuffer.get()); ++ } ++ ++ while (ioBuffer.hasRemaining()) { ++ socket.getOutputStream().write(ioBuffer.get()); ++ } ++ socket.getOutputStream().flush(); ++ } ++ ++ @Test ++ public void testTcpFallbackWhenTimeout() throws IOException { ++ testTcpFallbackWhenTimeout(true); ++ } ++ ++ @Test ++ public void testTcpFallbackFailedWhenTimeout() throws IOException { ++ testTcpFallbackWhenTimeout(false); ++ } ++ ++ private void testTcpFallbackWhenTimeout(boolean tcpSuccess) throws IOException { ++ ServerSocket serverSocket = new ServerSocket(); ++ serverSocket.setReuseAddress(true); ++ serverSocket.bind(new InetSocketAddress(NetUtil.LOCALHOST4, 0)); ++ ++ final String host = "somehost.netty.io"; ++ final String txt = "this is a txt record"; ++ final AtomicReference messageRef = new AtomicReference(); ++ ++ TestDnsServer dnsServer2 = new TestDnsServer(new RecordStore() { ++ @Override ++ public Set getRecords(QuestionRecord question) { ++ String name = question.getDomainName(); ++ if (name.equals(host)) { ++ return Collections.singleton( ++ new TestDnsServer.TestResourceRecord(name, RecordType.TXT, ++ Collections.singletonMap( ++ DnsAttribute.CHARACTER_STRING.toLowerCase(), txt))); ++ } ++ return null; ++ } ++ }) { ++ @Override ++ protected DnsMessage filterMessage(DnsMessage message) { ++ // Store a original message so we can replay it later on. ++ messageRef.set(message); ++ return null; ++ } ++ }; ++ DnsNameResolver resolver = null; ++ try { ++ DnsNameResolverBuilder builder = newResolver(); ++ final DatagramChannel datagramChannel = new NioDatagramChannel(); ++ ChannelFactory channelFactory = new ChannelFactory() { ++ @Override ++ public DatagramChannel newChannel() { ++ return datagramChannel; ++ } ++ }; ++ builder.channelFactory(channelFactory); ++ dnsServer2.start(null, (InetSocketAddress) serverSocket.getLocalSocketAddress()); ++ // If we are configured to use TCP as a fallback also bind a TCP socket ++ builder.socketChannelType(NioSocketChannel.class); ++ ++ builder.queryTimeoutMillis(1000) ++ .resolvedAddressTypes(ResolvedAddressTypes.IPV4_PREFERRED) ++ .maxQueriesPerResolve(16) ++ .nameServerProvider(new SingletonDnsServerAddressStreamProvider(dnsServer2.localAddress())); ++ resolver = builder.build(); ++ Future> envelopeFuture = resolver.query( ++ new DefaultDnsQuestion(host, DnsRecordType.TXT)); ++ ++ // If we are configured to use TCP as a fallback lets replay the dns message over TCP ++ Socket socket = serverSocket.accept(); ++ ++ if (tcpSuccess) { ++ responseViaSocket(socket, messageRef.get()); ++ socket.close(); ++ ++ // Let's wait until we received the envelope before closing the socket. ++ envelopeFuture.syncUninterruptibly(); ++ ++ AddressedEnvelope envelope = ++ envelopeFuture.syncUninterruptibly().getNow(); ++ assertNotNull(envelope.sender()); ++ ++ DnsResponse response = envelope.content(); ++ assertNotNull(response); ++ ++ assertEquals(DnsResponseCode.NOERROR, response.code()); ++ int count = response.count(DnsSection.ANSWER); ++ ++ assertEquals(1, count); ++ List texts = decodeTxt(response.recordAt(DnsSection.ANSWER, 0)); ++ assertEquals(1, texts.size()); ++ assertEquals(txt, texts.get(0)); ++ ++ assertFalse(envelope.content().isTruncated()); ++ assertTrue(envelope.release()); ++ } else { ++ // Just close the socket. This should cause the original exception to be used. ++ socket.close(); ++ Throwable error = envelopeFuture.awaitUninterruptibly().cause(); ++ assertThat(error, instanceOf(DnsNameResolverTimeoutException.class)); ++ assertThat(error.getSuppressed().length, greaterThanOrEqualTo(1)); ++ } ++ } finally { ++ dnsServer2.stop(); ++ if (resolver != null) { ++ resolver.close(); ++ } ++ serverSocket.close(); ++ } ++ } ++ + @Test + public void testCancelPromise() throws Exception { + final EventLoop eventLoop = group.next(); + +From fea7225dff68c16be94451e2c1c44ec442547e5a Mon Sep 17 00:00:00 2001 +From: Norman Maurer +Date: Fri, 29 Dec 2023 22:05:08 +0100 +Subject: [PATCH 2/4] Fix race in test + +--- + .../test/java/io/netty/resolver/dns/DnsNameResolverTest.java | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java b/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java +index 30277cb9a6c7..2cd9e6a4d9f9 100644 +--- a/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java ++++ b/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java +@@ -3423,10 +3423,10 @@ public DatagramChannel newChannel() { + + if (tcpSuccess) { + responseViaSocket(socket, messageRef.get()); +- socket.close(); + + // Let's wait until we received the envelope before closing the socket. + envelopeFuture.syncUninterruptibly(); ++ socket.close(); + + AddressedEnvelope envelope = + envelopeFuture.syncUninterruptibly().getNow(); + +From 6785c2d865fad37cf2a12963be99c8789aa82b15 Mon Sep 17 00:00:00 2001 +From: Norman Maurer +Date: Fri, 12 Jan 2024 12:23:06 +0100 +Subject: [PATCH 3/4] Fix compile error + +--- + .../src/main/java/io/netty/resolver/dns/DnsNameResolver.java | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java +index 9a63aa185dc9..4391b689e1a7 100644 +--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java ++++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java +@@ -470,8 +470,7 @@ public DnsNameResolver( + socketBootstrap.option(ChannelOption.SO_REUSEADDR, true) + .group(executor()) + .channelFactory(socketChannelFactory) +- .attr(DNS_PIPELINE_ATTRIBUTE, Boolean.TRUE) +- .handler(TCP_ENCODER); ++ .attr(DNS_PIPELINE_ATTRIBUTE, Boolean.TRUE); + if (queryTimeoutMillis > 0 && queryTimeoutMillis <= Integer.MAX_VALUE) { + // Set the connect timeout to the same as queryTimeout as otherwise it might take a long + // time for the query to fail in case of a connection timeout. + +From 3c8df3a4d4b0ffe8116e254de29ef52a94f9adc5 Mon Sep 17 00:00:00 2001 +From: Norman Maurer +Date: Fri, 12 Jan 2024 12:54:35 +0100 +Subject: [PATCH 4/4] Make tcp fallback on timeout configurable + +--- + .../resolver/dns/DatagramDnsQueryContext.java | 4 +- + .../netty/resolver/dns/DnsNameResolver.java | 41 ++++---------- + .../resolver/dns/DnsNameResolverBuilder.java | 54 ++++++++++++++++--- + .../netty/resolver/dns/DnsQueryContext.java | 13 +++-- + .../resolver/dns/TcpDnsQueryContext.java | 2 +- + .../resolver/dns/DnsNameResolverTest.java | 2 +- + 6 files changed, 70 insertions(+), 46 deletions(-) + +diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DatagramDnsQueryContext.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DatagramDnsQueryContext.java +index 4c5487069079..ca382dcbb114 100644 +--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DatagramDnsQueryContext.java ++++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DatagramDnsQueryContext.java +@@ -37,9 +37,9 @@ final class DatagramDnsQueryContext extends DnsQueryContext { + long queryTimeoutMillis, + DnsQuestion question, DnsRecord[] additionals, + Promise> promise, +- Bootstrap socketBootstrap) { ++ Bootstrap socketBootstrap, boolean retryWithTcpOnTimeout) { + super(channel, channelReadyFuture, nameServerAddr, queryContextManager, maxPayLoadSize, recursionDesired, +- queryTimeoutMillis, question, additionals, promise, socketBootstrap); ++ queryTimeoutMillis, question, additionals, promise, socketBootstrap, retryWithTcpOnTimeout); + } + + @Override +diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java +index 4391b689e1a7..d286bac27832 100644 +--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java ++++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java +@@ -279,6 +279,7 @@ protected DnsServerAddressStream initialValue() { + private final DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory; + private final boolean completeOncePreferredResolved; + private final Bootstrap socketBootstrap; ++ private final boolean retryWithTcpOnTimeout; + + private final int maxNumConsolidation; + private final Map>> inflightLookups; +@@ -382,44 +383,18 @@ public DnsNameResolver( + String[] searchDomains, + int ndots, + boolean decodeIdn) { +- this(eventLoop, channelFactory, null, resolveCache, NoopDnsCnameCache.INSTANCE, authoritativeDnsServerCache, ++ this(eventLoop, channelFactory, null, false, resolveCache, ++ NoopDnsCnameCache.INSTANCE, authoritativeDnsServerCache, null, + dnsQueryLifecycleObserverFactory, queryTimeoutMillis, resolvedAddressTypes, recursionDesired, + maxQueriesPerResolve, traceEnabled, maxPayloadSize, optResourceEnabled, hostsFileEntriesResolver, +- dnsServerAddressStreamProvider, searchDomains, ndots, decodeIdn, false); +- } +- +- DnsNameResolver( +- EventLoop eventLoop, +- ChannelFactory channelFactory, +- ChannelFactory socketChannelFactory, +- final DnsCache resolveCache, +- final DnsCnameCache cnameCache, +- final AuthoritativeDnsServerCache authoritativeDnsServerCache, +- DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory, +- long queryTimeoutMillis, +- ResolvedAddressTypes resolvedAddressTypes, +- boolean recursionDesired, +- int maxQueriesPerResolve, +- boolean traceEnabled, +- int maxPayloadSize, +- boolean optResourceEnabled, +- HostsFileEntriesResolver hostsFileEntriesResolver, +- DnsServerAddressStreamProvider dnsServerAddressStreamProvider, +- String[] searchDomains, +- int ndots, +- boolean decodeIdn, +- boolean completeOncePreferredResolved) { +- this(eventLoop, channelFactory, socketChannelFactory, resolveCache, cnameCache, authoritativeDnsServerCache, +- null, dnsQueryLifecycleObserverFactory, queryTimeoutMillis, resolvedAddressTypes, +- recursionDesired, maxQueriesPerResolve, traceEnabled, maxPayloadSize, optResourceEnabled, +- hostsFileEntriesResolver, dnsServerAddressStreamProvider, searchDomains, ndots, decodeIdn, +- completeOncePreferredResolved, 0); ++ dnsServerAddressStreamProvider, searchDomains, ndots, decodeIdn, false, 0); + } + + DnsNameResolver( + EventLoop eventLoop, + ChannelFactory channelFactory, + ChannelFactory socketChannelFactory, ++ boolean retryWithTcpOnTimeout, + final DnsCache resolveCache, + final DnsCnameCache cnameCache, + final AuthoritativeDnsServerCache authoritativeDnsServerCache, +@@ -463,6 +438,7 @@ public DnsNameResolver( + this.ndots = ndots >= 0 ? ndots : DEFAULT_OPTIONS.ndots(); + this.decodeIdn = decodeIdn; + this.completeOncePreferredResolved = completeOncePreferredResolved; ++ this.retryWithTcpOnTimeout = retryWithTcpOnTimeout; + if (socketChannelFactory == null) { + socketBootstrap = null; + } else { +@@ -470,7 +446,8 @@ public DnsNameResolver( + socketBootstrap.option(ChannelOption.SO_REUSEADDR, true) + .group(executor()) + .channelFactory(socketChannelFactory) +- .attr(DNS_PIPELINE_ATTRIBUTE, Boolean.TRUE); ++ .attr(DNS_PIPELINE_ATTRIBUTE, Boolean.TRUE) ++ .handler(NOOP_HANDLER); + if (queryTimeoutMillis > 0 && queryTimeoutMillis <= Integer.MAX_VALUE) { + // Set the connect timeout to the same as queryTimeout as otherwise it might take a long + // time for the query to fail in case of a connection timeout. +@@ -1360,7 +1337,7 @@ final Future> query0( + try { + DnsQueryContext queryContext = new DatagramDnsQueryContext(ch, channelReadyPromise, nameServerAddr, + queryContextManager, payloadSize, isRecursionDesired(), queryTimeoutMillis(), question, additionals, +- castPromise, socketBootstrap); ++ castPromise, socketBootstrap, retryWithTcpOnTimeout); + ChannelFuture future = queryContext.writeQuery(flush); + queryLifecycleObserver.queryWritten(nameServerAddr, future); + return castPromise; +diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverBuilder.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverBuilder.java +index ddd26d501b9b..0745ac45c285 100644 +--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverBuilder.java ++++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverBuilder.java +@@ -47,6 +47,8 @@ public final class DnsNameResolverBuilder { + volatile EventLoop eventLoop; + private ChannelFactory channelFactory; + private ChannelFactory socketChannelFactory; ++ private boolean retryOnTimeout; ++ + private DnsCache resolveCache; + private DnsCnameCache cnameCache; + private AuthoritativeDnsServerCache authoritativeDnsServerCache; +@@ -143,7 +145,44 @@ public DnsNameResolverBuilder channelType(Class chann + * @return {@code this} + */ + public DnsNameResolverBuilder socketChannelFactory(ChannelFactory channelFactory) { ++ return socketChannelFactory(channelFactory, false); ++ } ++ ++ /** ++ * Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type for ++ *
TCP fallback if needed. ++ * Use as an alternative to {@link #socketChannelFactory(ChannelFactory)}. ++ * ++ * TCP fallback is not enabled by default and must be enabled by providing a non-null ++ * {@code channelType} for this method. ++ * ++ * @param channelType the type or {@code null} if TCP fallback ++ * should not be supported. By default, TCP fallback is not enabled. ++ * @return {@code this} ++ */ ++ public DnsNameResolverBuilder socketChannelType(Class channelType) { ++ return socketChannelType(channelType, false); ++ } ++ ++ /** ++ * Sets the {@link ChannelFactory} that will create a {@link SocketChannel} for ++ * TCP fallback if needed. ++ * ++ * TCP fallback is not enabled by default and must be enabled by providing a non-null ++ * {@link ChannelFactory} for this method. ++ * ++ * @param channelFactory the {@link ChannelFactory} or {@code null} ++ * if TCP fallback should not be supported. ++ * By default, TCP fallback is not enabled. ++ * @param retryOnTimeout if {@code true} the {@link DnsNameResolver} will also fallback to TCP if a timeout ++ * was detected, if {@code false} it will only try to use TCP if the response was marked ++ * as truncated. ++ * @return {@code this} ++ */ ++ public DnsNameResolverBuilder socketChannelFactory( ++ ChannelFactory channelFactory, boolean retryOnTimeout) { + this.socketChannelFactory = channelFactory; ++ this.retryOnTimeout = retryOnTimeout; + return this; + } + +@@ -157,13 +196,17 @@ public DnsNameResolverBuilder socketChannelFactory(ChannelFactoryTCP fallback + * should not be supported. By default, TCP fallback is not enabled. ++ * @param retryOnTimeout if {@code true} the {@link DnsNameResolver} will also fallback to TCP if a timeout ++ * was detected, if {@code false} it will only try to use TCP if the response was marked ++ * as truncated. + * @return {@code this} + */ +- public DnsNameResolverBuilder socketChannelType(Class channelType) { ++ public DnsNameResolverBuilder socketChannelType( ++ Class channelType, boolean retryOnTimeout) { + if (channelType == null) { +- return socketChannelFactory(null); ++ return socketChannelFactory(null, retryOnTimeout); + } +- return socketChannelFactory(new ReflectiveChannelFactory(channelType)); ++ return socketChannelFactory(new ReflectiveChannelFactory(channelType), retryOnTimeout); + } + + /** +@@ -528,6 +571,7 @@ public DnsNameResolver build() { + eventLoop, + channelFactory, + socketChannelFactory, ++ retryOnTimeout, + resolveCache, + cnameCache, + authoritativeDnsServerCache, +@@ -565,9 +609,7 @@ public DnsNameResolverBuilder copy() { + copiedBuilder.channelFactory(channelFactory); + } + +- if (socketChannelFactory != null) { +- copiedBuilder.socketChannelFactory(socketChannelFactory); +- } ++ copiedBuilder.socketChannelFactory(socketChannelFactory, retryOnTimeout); + + if (resolveCache != null) { + copiedBuilder.resolveCache(resolveCache); +diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java +index 1741e25dd543..da1091b1d160 100644 +--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java ++++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java +@@ -75,6 +75,8 @@ abstract class DnsQueryContext { + private final boolean recursionDesired; + + private final Bootstrap socketBootstrap; ++ ++ private final boolean retryWithTcpOnTimeout; + private final long queryTimeoutMillis; + + private volatile Future timeoutFuture; +@@ -90,7 +92,9 @@ abstract class DnsQueryContext { + long queryTimeoutMillis, + DnsQuestion question, + DnsRecord[] additionals, +- Promise> promise, Bootstrap socketBootstrap) { ++ Promise> promise, ++ Bootstrap socketBootstrap, ++ boolean retryWithTcpOnTimeout) { + this.channel = checkNotNull(channel, "channel"); + this.queryContextManager = checkNotNull(queryContextManager, "queryContextManager"); + this.channelReadyFuture = checkNotNull(channelReadyFuture, "channelReadyFuture"); +@@ -101,6 +105,7 @@ abstract class DnsQueryContext { + this.recursionDesired = recursionDesired; + this.queryTimeoutMillis = queryTimeoutMillis; + this.socketBootstrap = socketBootstrap; ++ this.retryWithTcpOnTimeout = retryWithTcpOnTimeout; + + if (maxPayLoadSize > 0 && + // Only add the extra OPT record if there is not already one. This is required as only one is allowed +@@ -313,7 +318,7 @@ public void run() { + */ + void finishSuccess(AddressedEnvelope envelope, boolean truncated) { + // Check if the response was not truncated or if a fallback to TCP is possible. +- if (!truncated || !retryWithTCP(envelope)) { ++ if (!truncated || !retryWithTcp(envelope)) { + final DnsResponse res = envelope.content(); + if (res.count(DnsSection.QUESTION) != 1) { + logger.warn("{} Received a DNS response with invalid number of questions. Expected: 1, found: {}", +@@ -358,7 +363,7 @@ final boolean finishFailure(String message, Throwable cause, boolean timeout) { + // This was caused by a timeout so use DnsNameResolverTimeoutException to allow the user to + // handle it special (like retry the query). + e = new DnsNameResolverTimeoutException(nameServerAddr, question, buf.toString()); +- if (retryWithTCP(e)) { ++ if (retryWithTcpOnTimeout && retryWithTcp(e)) { + // We did successfully retry with TCP. + return false; + } +@@ -375,7 +380,7 @@ final boolean finishFailure(String message, Throwable cause, boolean timeout) { + * @return {@code true} if retry via TCP is supported and so the ownership of + * {@code originalResult} was transferred, {@code false} otherwise. + */ +- private boolean retryWithTCP(final Object originalResult) { ++ private boolean retryWithTcp(final Object originalResult) { + if (socketBootstrap == null) { + return false; + } +diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/TcpDnsQueryContext.java b/resolver-dns/src/main/java/io/netty/resolver/dns/TcpDnsQueryContext.java +index 2111b67aad9c..f8022a8c6a77 100644 +--- a/resolver-dns/src/main/java/io/netty/resolver/dns/TcpDnsQueryContext.java ++++ b/resolver-dns/src/main/java/io/netty/resolver/dns/TcpDnsQueryContext.java +@@ -38,7 +38,7 @@ final class TcpDnsQueryContext extends DnsQueryContext { + Promise> promise) { + super(channel, channelReadyFuture, nameServerAddr, queryContextManager, maxPayLoadSize, recursionDesired, + // No retry via TCP. +- queryTimeoutMillis, question, additionals, promise, null); ++ queryTimeoutMillis, question, additionals, promise, null, false); + } + + @Override +diff --git a/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java b/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java +index 2cd9e6a4d9f9..b873a2f55510 100644 +--- a/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java ++++ b/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java +@@ -3408,7 +3408,7 @@ public DatagramChannel newChannel() { + builder.channelFactory(channelFactory); + dnsServer2.start(null, (InetSocketAddress) serverSocket.getLocalSocketAddress()); + // If we are configured to use TCP as a fallback also bind a TCP socket +- builder.socketChannelType(NioSocketChannel.class); ++ builder.socketChannelType(NioSocketChannel.class, true); + + builder.queryTimeoutMillis(1000) + .resolvedAddressTypes(ResolvedAddressTypes.IPV4_PREFERRED) diff --git a/patches/13765.patch b/patches/13765.patch new file mode 100644 index 0000000..ddf11f2 --- /dev/null +++ b/patches/13765.patch @@ -0,0 +1,65 @@ +From 2558a8ff33cd5b39e1273508d757db8443daaf27 Mon Sep 17 00:00:00 2001 +From: Francesco Nigro +Date: Fri, 5 Jan 2024 21:01:27 +0100 +Subject: [PATCH] Save HTTP 2 pseudo-header lower-case validation + +Motivation: + +pseudo-header constants doesn't requires any further validation + +Modifications: + +anticipate pseudoheader lookup on http 2 header name validation, saving any further validation, if positive + +Result: + +Faster validations over known pseudo-headers +--- + .../codec/http2/DefaultHttp2Headers.java | 19 ++++++++++--------- + 1 file changed, 10 insertions(+), 9 deletions(-) + +diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java +index 60a2925873ca..eafb26336852 100644 +--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java ++++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java +@@ -24,8 +24,8 @@ + + import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; + import static io.netty.handler.codec.http2.Http2Exception.connectionError; +-import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.getPseudoHeader; + import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.hasPseudoHeaderFormat; ++import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.isPseudoHeader; + import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER; + import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER; + import static io.netty.util.AsciiString.isUpperCase; +@@ -47,6 +47,15 @@ public void validateName(CharSequence name) { + "empty headers are not allowed [%s]", name)); + } + ++ if (hasPseudoHeaderFormat(name)) { ++ if (!isPseudoHeader(name)) { ++ PlatformDependent.throwException(connectionError( ++ PROTOCOL_ERROR, "Invalid HTTP/2 pseudo-header '%s' encountered.", name)); ++ } ++ // no need for lower-case validation, we trust our own pseudo header constants ++ return; ++ } ++ + if (name instanceof AsciiString) { + final int index; + try { +@@ -72,14 +81,6 @@ public void validateName(CharSequence name) { + } + } + } +- +- if (hasPseudoHeaderFormat(name)) { +- final Http2Headers.PseudoHeaderName pseudoHeader = getPseudoHeader(name); +- if (pseudoHeader == null) { +- PlatformDependent.throwException(connectionError( +- PROTOCOL_ERROR, "Invalid HTTP/2 pseudo-header '%s' encountered.", name)); +- } +- } + } + }; + diff --git a/patches/13778.patch b/patches/13778.patch new file mode 100644 index 0000000..da69b0e --- /dev/null +++ b/patches/13778.patch @@ -0,0 +1,36 @@ +From 88de2875dd8c75c1921c2bbb22775d2cb1432396 Mon Sep 17 00:00:00 2001 +From: Norman Maurer +Date: Thu, 11 Jan 2024 17:50:40 +0100 +Subject: [PATCH] DnsNameResolver: Limit connect timeout to query timeout + +Motivation: + +We should not use the default connect timeout (10s) but better use the query timeout as the limit + +Modifications: + +Use query timeout as connect timeout if any is configured + +Result: + +Faster failing connect timeouts when using TCP fallback +--- + .../src/main/java/io/netty/resolver/dns/DnsNameResolver.java | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java +index 535b87cee39f..6fbf86fc29d9 100644 +--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java ++++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java +@@ -466,6 +466,11 @@ public DnsNameResolver( + .channelFactory(socketChannelFactory) + .attr(DNS_PIPELINE_ATTRIBUTE, Boolean.TRUE) + .handler(TCP_ENCODER); ++ if (queryTimeoutMillis > 0 && queryTimeoutMillis <= Integer.MAX_VALUE) { ++ // Set the connect timeout to the same as queryTimeout as otherwise it might take a long ++ // time for the query to fail in case of a connection timeout. ++ socketBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) queryTimeoutMillis); ++ } + } + switch (this.resolvedAddressTypes) { + case IPV4_ONLY: diff --git a/patches/13779.patch b/patches/13779.patch new file mode 100644 index 0000000..687e83e --- /dev/null +++ b/patches/13779.patch @@ -0,0 +1,254 @@ +From 1352fa5543638d421042f1d69c8fc2c6f4603968 Mon Sep 17 00:00:00 2001 +From: Scott Mitchell +Date: Thu, 11 Jan 2024 11:24:07 -0800 +Subject: [PATCH] h2: propagate stream close without read pending, avoid SOOE + if autoRead == false + +Motivation: +AbstractHttp2StreamChannel requires that there is a channel.read() +pending in order to notify of channel inactive events. For use cases +that don't use auto read the application could have read the frame +corresponding to end of stream but not get the channel inactive. +If read is called from within channelReadComplete this creates +a reentrant loop into AbstractHttp2StreamChannel#doBeginRead that +may cause a StackOverflowException. + +Modifications: +- propagate stream/channel closed event if possible (e.g. no pending +data) without invoking read(). +- adjust readPending state to prevent StackOverflowException and +let the read loop continue. +--- + .../http2/AbstractHttp2StreamChannel.java | 84 +++++++++++-------- + .../codec/http2/Http2MultiplexTest.java | 73 +++++++++++++++- + 2 files changed, 121 insertions(+), 36 deletions(-) + +diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2StreamChannel.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2StreamChannel.java +index 49882acf1ad7..c768576d2aea 100644 +--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2StreamChannel.java ++++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2StreamChannel.java +@@ -605,7 +605,7 @@ void fireChildRead(Http2Frame frame) { + if (allocHandle.continueReading()) { + maybeAddChannelToReadCompletePendingQueue(); + } else { +- unsafe.notifyReadComplete(allocHandle, true); ++ unsafe.notifyReadComplete(allocHandle, true, false); + } + } else { + if (inboundBuffer == null) { +@@ -618,7 +618,7 @@ void fireChildRead(Http2Frame frame) { + void fireChildReadComplete() { + assert eventLoop().inEventLoop(); + assert readStatus != ReadStatus.IDLE || !readCompletePending; +- unsafe.notifyReadComplete(unsafe.recvBufAllocHandle(), false); ++ unsafe.notifyReadComplete(unsafe.recvBufAllocHandle(), false, false); + } + + final void closeWithError(Http2Error error) { +@@ -851,35 +851,47 @@ private Object pollQueuedMessage() { + } + + void doBeginRead() { +- // Process messages until there are none left (or the user stopped requesting) and also handle EOS. +- while (readStatus != ReadStatus.IDLE) { +- Object message = pollQueuedMessage(); +- if (message == null) { +- if (readEOS) { +- unsafe.closeForcibly(); +- } +- // We need to double check that there is nothing left to flush such as a +- // window update frame. ++ if (readStatus == ReadStatus.IDLE) { ++ // Don't wait for the user to request a read to notify of channel closure. ++ if (readEOS && (inboundBuffer == null || inboundBuffer.isEmpty())) { ++ // Double check there is nothing left to flush such as a window update frame. + flush(); +- break; +- } +- final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle(); +- allocHandle.reset(config()); +- boolean continueReading = false; +- do { +- doRead0((Http2Frame) message, allocHandle); +- } while ((readEOS || (continueReading = allocHandle.continueReading())) +- && (message = pollQueuedMessage()) != null); +- +- if (continueReading && isParentReadInProgress() && !readEOS) { +- // Currently the parent and child channel are on the same EventLoop thread. If the parent is +- // currently reading it is possible that more frames will be delivered to this child channel. In +- // the case that this child channel still wants to read we delay the channelReadComplete on this +- // child channel until the parent is done reading. +- maybeAddChannelToReadCompletePendingQueue(); +- } else { +- notifyReadComplete(allocHandle, true); ++ unsafe.closeForcibly(); + } ++ } else { ++ do { // Process messages until there are none left (or the user stopped requesting) and also handle EOS. ++ Object message = pollQueuedMessage(); ++ if (message == null) { ++ // Double check there is nothing left to flush such as a window update frame. ++ flush(); ++ if (readEOS) { ++ unsafe.closeForcibly(); ++ } ++ break; ++ } ++ final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle(); ++ allocHandle.reset(config()); ++ boolean continueReading = false; ++ do { ++ doRead0((Http2Frame) message, allocHandle); ++ } while ((readEOS || (continueReading = allocHandle.continueReading())) ++ && (message = pollQueuedMessage()) != null); ++ ++ if (continueReading && isParentReadInProgress() && !readEOS) { ++ // Currently the parent and child channel are on the same EventLoop thread. If the parent is ++ // currently reading it is possible that more frames will be delivered to this child channel. In ++ // the case that this child channel still wants to read we delay the channelReadComplete on this ++ // child channel until the parent is done reading. ++ maybeAddChannelToReadCompletePendingQueue(); ++ } else { ++ notifyReadComplete(allocHandle, true, true); ++ ++ // While in the read loop reset the readState AFTER calling readComplete (or other pipeline ++ // callbacks) to prevents re-entry into this method (if autoRead is disabled and the user calls ++ // read on each readComplete) and StackOverflowException. ++ resetReadStatus(); ++ } ++ } while (readStatus != ReadStatus.IDLE); + } + } + +@@ -908,17 +920,21 @@ private void updateLocalWindowIfNeeded() { + } + } + +- void notifyReadComplete(RecvByteBufAllocator.Handle allocHandle, boolean forceReadComplete) { ++ private void resetReadStatus() { ++ readStatus = readStatus == ReadStatus.REQUESTED ? ReadStatus.IN_PROGRESS : ReadStatus.IDLE; ++ } ++ ++ void notifyReadComplete(RecvByteBufAllocator.Handle allocHandle, boolean forceReadComplete, ++ boolean inReadLoop) { + if (!readCompletePending && !forceReadComplete) { + return; + } + // Set to false just in case we added the channel multiple times before. + readCompletePending = false; + +- if (readStatus == ReadStatus.REQUESTED) { +- readStatus = ReadStatus.IN_PROGRESS; +- } else { +- readStatus = ReadStatus.IDLE; ++ if (!inReadLoop) { ++ // While in the read loop we reset the state after calling pipeline methods to prevent StackOverflow. ++ resetReadStatus(); + } + + allocHandle.readComplete(); +diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexTest.java +index f277dbb12ef1..b1f28b14b94b 100644 +--- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexTest.java ++++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexTest.java +@@ -58,6 +58,7 @@ + import java.util.concurrent.atomic.AtomicInteger; + import java.util.concurrent.atomic.AtomicReference; + ++import static io.netty.handler.codec.http2.Http2Error.NO_ERROR; + import static io.netty.handler.codec.http2.Http2TestUtil.anyChannelPromise; + import static io.netty.handler.codec.http2.Http2TestUtil.anyHttp2Settings; + import static io.netty.handler.codec.http2.Http2TestUtil.assertEqualsAndRelease; +@@ -477,6 +478,74 @@ public void channelReadShouldRespectAutoRead() { + verifyFramesMultiplexedToCorrectChannel(childChannel, inboundHandler, 2); + } + ++ @Test ++ public void noAutoReadWithReentrantReadDoesNotSOOE() { ++ final AtomicBoolean shouldRead = new AtomicBoolean(); ++ Consumer ctxConsumer = new Consumer() { ++ @Override ++ public void accept(ChannelHandlerContext obj) { ++ if (shouldRead.get()) { ++ obj.read(); ++ } ++ } ++ }; ++ LastInboundHandler inboundHandler = new LastInboundHandler(ctxConsumer); ++ AtomicInteger maxReads = new AtomicInteger(1); ++ Http2StreamChannel childChannel = newInboundStream(3, false, maxReads, inboundHandler); ++ assertTrue(childChannel.config().isAutoRead()); ++ Http2HeadersFrame headersFrame = inboundHandler.readInbound(); ++ assertNotNull(headersFrame); ++ ++ childChannel.config().setAutoRead(false); ++ ++ final int maxWrites = 10000; // enough writes to generated SOOE. ++ for (int i = 0; i < maxWrites; ++i) { ++ frameInboundWriter.writeInboundData(childChannel.stream().id(), bb(String.valueOf(i)), 0, false); ++ } ++ frameInboundWriter.writeInboundData(childChannel.stream().id(), bb(String.valueOf(maxWrites)), 0, true); ++ shouldRead.set(true); ++ childChannel.read(); ++ ++ for (int i = 0; i < maxWrites; ++i) { ++ Http2DataFrame dataFrame0 = inboundHandler.readInbound(); ++ assertNotNull(dataFrame0); ++ release(dataFrame0); ++ } ++ Http2DataFrame dataFrame0 = inboundHandler.readInbound(); ++ assertTrue(dataFrame0.isEndStream()); ++ release(dataFrame0); ++ ++ verifyFramesMultiplexedToCorrectChannel(childChannel, inboundHandler, 0); ++ } ++ ++ @Test ++ public void readNotRequiredToEndStream() { ++ LastInboundHandler inboundHandler = new LastInboundHandler(); ++ AtomicInteger maxReads = new AtomicInteger(1); ++ Http2StreamChannel childChannel = newInboundStream(3, false, maxReads, inboundHandler); ++ assertTrue(childChannel.config().isAutoRead()); ++ ++ childChannel.config().setAutoRead(false); ++ ++ Http2HeadersFrame headersFrame = inboundHandler.readInbound(); ++ assertNotNull(headersFrame); ++ ++ assertNull(inboundHandler.readInbound()); ++ ++ frameInboundWriter.writeInboundRstStream(childChannel.stream().id(), NO_ERROR.code()); ++ ++ assertFalse(inboundHandler.isChannelActive()); ++ childChannel.closeFuture().syncUninterruptibly(); ++ ++ Http2ResetFrame resetFrame = useUserEventForResetFrame() ? inboundHandler.readUserEvent() : ++ inboundHandler.readInbound(); ++ ++ assertEquals(childChannel.stream(), resetFrame.stream()); ++ assertEquals(NO_ERROR.code(), resetFrame.errorCode()); ++ ++ verifyFramesMultiplexedToCorrectChannel(childChannel, inboundHandler, 0); ++ } ++ + @Test + public void channelReadShouldRespectAutoReadAndNotProduceNPE() throws Exception { + LastInboundHandler inboundHandler = new LastInboundHandler(); +@@ -1095,7 +1164,7 @@ public void channelReadComplete(ChannelHandlerContext ctx) { + childChannel.config().setAutoRead(true); + numReads.set(1); + +- frameInboundWriter.writeInboundRstStream(childChannel.stream().id(), Http2Error.NO_ERROR.code()); ++ frameInboundWriter.writeInboundRstStream(childChannel.stream().id(), NO_ERROR.code()); + + // Detecting EOS should flush all pending data regardless of read calls. + assertEqualsAndRelease(dataFrame2, inboundHandler.readInbound()); +@@ -1111,7 +1180,7 @@ public void channelReadComplete(ChannelHandlerContext ctx) { + inboundHandler.readInbound(); + + assertEquals(childChannel.stream(), resetFrame.stream()); +- assertEquals(Http2Error.NO_ERROR.code(), resetFrame.errorCode()); ++ assertEquals(NO_ERROR.code(), resetFrame.errorCode()); + + assertNull(inboundHandler.readInbound()); + diff --git a/settings.gradle b/settings.gradle index 244506e..85840a6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -62,6 +62,7 @@ include 'netty-channel-epoll' include 'netty-channel-epoll-native' include 'netty-channel-sctp' include 'netty-channel-unix' +include 'netty-channel-unix-native' include 'netty-handler' include 'netty-handler-codec' include 'netty-handler-codec-compression' @@ -78,7 +79,7 @@ include 'netty-handler-ssl-bouncycastle' include 'netty-internal-tcnative' include 'netty-jctools' include 'netty-resolver' -include 'netty-tcnative-boringssl-static' +include 'netty-tcnative-boringssl-static-native' include 'netty-testsuite' include 'netty-util' include 'netty-zlib'