Correctly handle IPV6-mapped-IPV4 addresses in native code when receiving datagrams (#9560)
Motivation:
291f80733a
introduced a change to use a byte[] to construct the InetAddress when receiving datagram messages to reduce the overhead. Unfortunally it introduced a regression when handling IPv6-mapped-IPv4 addresses and so produced an IndexOutOfBoundsException when trying to fill the byte[] in native code.
Modifications:
- Correctly use the offset on the pointer of the address.
- Add testcase
- Make tests more robust and include more details when the test fails
Result:
No more IndexOutOfBoundsException
This commit is contained in:
parent
f26840478a
commit
19a12fba4c
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
package io.netty.testsuite.transport.socket;
|
||||
|
||||
import io.netty.util.NetUtil;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
public class DatagramUnicastIPv6MappedTest extends DatagramUnicastIPv6Test {
|
||||
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return new InetSocketAddress(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InetSocketAddress sendToAddress(InetSocketAddress serverAddress) {
|
||||
InetAddress addr = serverAddress.getAddress();
|
||||
if (addr.isAnyLocalAddress()) {
|
||||
return new InetSocketAddress(NetUtil.LOCALHOST4, serverAddress.getPort());
|
||||
}
|
||||
return serverAddress;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
package io.netty.testsuite.transport.socket;
|
||||
|
||||
import io.netty.channel.socket.InternetProtocolFamily;
|
||||
import org.junit.Assume;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.StandardProtocolFamily;
|
||||
import java.nio.channels.Channel;
|
||||
import java.nio.channels.spi.SelectorProvider;
|
||||
|
||||
public class DatagramUnicastIPv6Test extends DatagramUnicastTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void assumeIpv6Supported() {
|
||||
try {
|
||||
Channel channel = SelectorProvider.provider().openDatagramChannel(StandardProtocolFamily.INET6);
|
||||
channel.close();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
Assume.assumeNoException("IPv6 not supported", e);
|
||||
} catch (IOException ignore) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected InternetProtocolFamily internetProtocolFamily() {
|
||||
return InternetProtocolFamily.IPv6;
|
||||
}
|
||||
}
|
@ -27,8 +27,11 @@ import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.socket.DatagramChannel;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import io.netty.util.NetUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.NotYetConnectedException;
|
||||
@ -36,6 +39,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@ -171,30 +175,27 @@ public class DatagramUnicastTest extends AbstractDatagramTest {
|
||||
}
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(count);
|
||||
sc = setupServerChannel(sb, bytes, sender, latch, false);
|
||||
AtomicReference<Throwable> errorRef = new AtomicReference<Throwable>();
|
||||
sc = setupServerChannel(sb, bytes, sender, latch, errorRef, false);
|
||||
|
||||
InetSocketAddress addr = (InetSocketAddress) sc.localAddress();
|
||||
InetSocketAddress addr = sendToAddress((InetSocketAddress) sc.localAddress());
|
||||
List<ChannelFuture> futures = new ArrayList<ChannelFuture>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
switch (wrapType) {
|
||||
case DUP:
|
||||
cc.write(new DatagramPacket(buf.retainedDuplicate(), addr));
|
||||
break;
|
||||
case SLICE:
|
||||
cc.write(new DatagramPacket(buf.retainedSlice(), addr));
|
||||
break;
|
||||
case READ_ONLY:
|
||||
cc.write(new DatagramPacket(buf.retain().asReadOnly(), addr));
|
||||
break;
|
||||
case NONE:
|
||||
cc.write(new DatagramPacket(buf.retain(), addr));
|
||||
break;
|
||||
default:
|
||||
throw new Error("unknown wrap type: " + wrapType);
|
||||
}
|
||||
futures.add(write(cc, buf, addr, wrapType));
|
||||
}
|
||||
// release as we used buf.retain() before
|
||||
cc.flush();
|
||||
assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||
|
||||
for (ChannelFuture future: futures) {
|
||||
future.sync();
|
||||
}
|
||||
if (!latch.await(10, TimeUnit.SECONDS)) {
|
||||
Throwable error = errorRef.get();
|
||||
if (error != null) {
|
||||
throw error;
|
||||
}
|
||||
fail();
|
||||
}
|
||||
} finally {
|
||||
// release as we used buf.retain() before
|
||||
buf.release();
|
||||
@ -204,6 +205,20 @@ public class DatagramUnicastTest extends AbstractDatagramTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static ChannelFuture write(Channel cc, ByteBuf buf, InetSocketAddress remote, WrapType wrapType) {
|
||||
switch (wrapType) {
|
||||
case DUP:
|
||||
return cc.write(new DatagramPacket(buf.retainedDuplicate(), remote));
|
||||
case SLICE:
|
||||
return cc.write(new DatagramPacket(buf.retainedSlice(), remote));
|
||||
case READ_ONLY:
|
||||
return cc.write(new DatagramPacket(buf.retain().asReadOnly(), remote));
|
||||
case NONE:
|
||||
return cc.write(new DatagramPacket(buf.retain(), remote));
|
||||
default:
|
||||
throw new Error("unknown wrap type: " + wrapType);
|
||||
}
|
||||
}
|
||||
private void testSimpleSendWithConnect(Bootstrap sb, Bootstrap cb, ByteBuf buf, final byte[] bytes, int count)
|
||||
throws Throwable {
|
||||
try {
|
||||
@ -218,20 +233,32 @@ public class DatagramUnicastTest extends AbstractDatagramTest {
|
||||
private void testSimpleSendWithConnect0(Bootstrap sb, Bootstrap cb, ByteBuf buf, final byte[] bytes, int count,
|
||||
WrapType wrapType) throws Throwable {
|
||||
final CountDownLatch clientLatch = new CountDownLatch(count);
|
||||
|
||||
final AtomicReference<Throwable> clientErrorRef = new AtomicReference<Throwable>();
|
||||
cb.handler(new SimpleChannelInboundHandler<DatagramPacket>() {
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
|
||||
ByteBuf buf = msg.content();
|
||||
assertEquals(bytes.length, buf.readableBytes());
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
assertEquals(bytes[i], buf.getByte(buf.readerIndex() + i));
|
||||
try {
|
||||
ByteBuf buf = msg.content();
|
||||
assertEquals(bytes.length, buf.readableBytes());
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
assertEquals(bytes[i], buf.getByte(buf.readerIndex() + i));
|
||||
}
|
||||
|
||||
InetSocketAddress localAddress = (InetSocketAddress) ctx.channel().localAddress();
|
||||
if (localAddress.getAddress().isAnyLocalAddress()) {
|
||||
assertEquals(localAddress.getPort(), msg.recipient().getPort());
|
||||
} else {
|
||||
// Test that the channel's localAddress is equal to the message's recipient
|
||||
assertEquals(localAddress, msg.recipient());
|
||||
}
|
||||
} finally {
|
||||
clientLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the channel's localAddress is equal to the message's recipient
|
||||
assertEquals(ctx.channel().localAddress(), msg.recipient());
|
||||
|
||||
clientLatch.countDown();
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
clientErrorRef.compareAndSet(null, cause);
|
||||
}
|
||||
});
|
||||
|
||||
@ -239,10 +266,11 @@ public class DatagramUnicastTest extends AbstractDatagramTest {
|
||||
DatagramChannel cc = null;
|
||||
try {
|
||||
final CountDownLatch latch = new CountDownLatch(count);
|
||||
final AtomicReference<Throwable> errorRef = new AtomicReference<Throwable>();
|
||||
cc = (DatagramChannel) cb.bind(newSocketAddress()).sync().channel();
|
||||
sc = setupServerChannel(sb, bytes, cc.localAddress(), latch, true);
|
||||
sc = setupServerChannel(sb, bytes, cc.localAddress(), latch, errorRef, true);
|
||||
|
||||
cc.connect(sc.localAddress()).syncUninterruptibly();
|
||||
cc.connect(sendToAddress((InetSocketAddress) sc.localAddress())).syncUninterruptibly();
|
||||
|
||||
List<ChannelFuture> futures = new ArrayList<ChannelFuture>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
@ -254,8 +282,20 @@ public class DatagramUnicastTest extends AbstractDatagramTest {
|
||||
future.sync();
|
||||
}
|
||||
|
||||
assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||
assertTrue(clientLatch.await(10, TimeUnit.SECONDS));
|
||||
if (!latch.await(10, TimeUnit.SECONDS)) {
|
||||
Throwable cause = errorRef.get();
|
||||
if (cause != null) {
|
||||
throw cause;
|
||||
}
|
||||
fail();
|
||||
}
|
||||
if (!clientLatch.await(10, TimeUnit.SECONDS)) {
|
||||
Throwable cause = clientErrorRef.get();
|
||||
if (cause != null) {
|
||||
throw cause;
|
||||
}
|
||||
fail();
|
||||
}
|
||||
assertTrue(cc.isConnected());
|
||||
|
||||
// Test what happens when we call disconnect()
|
||||
@ -292,7 +332,8 @@ public class DatagramUnicastTest extends AbstractDatagramTest {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private Channel setupServerChannel(Bootstrap sb, final byte[] bytes, final SocketAddress sender,
|
||||
final CountDownLatch latch, final boolean echo)
|
||||
final CountDownLatch latch, final AtomicReference<Throwable> errorRef,
|
||||
final boolean echo)
|
||||
throws Throwable {
|
||||
sb.handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
@ -300,25 +341,38 @@ public class DatagramUnicastTest extends AbstractDatagramTest {
|
||||
ch.pipeline().addLast(new SimpleChannelInboundHandler<DatagramPacket>() {
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
|
||||
if (sender == null) {
|
||||
assertNotNull(msg.sender());
|
||||
} else {
|
||||
assertEquals(sender, msg.sender());
|
||||
}
|
||||
try {
|
||||
if (sender == null) {
|
||||
assertNotNull(msg.sender());
|
||||
} else {
|
||||
InetSocketAddress senderAddress = (InetSocketAddress) sender;
|
||||
if (senderAddress.getAddress().isAnyLocalAddress()) {
|
||||
assertEquals(senderAddress.getPort(), msg.sender().getPort());
|
||||
} else {
|
||||
assertEquals(sender, msg.sender());
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuf buf = msg.content();
|
||||
assertEquals(bytes.length, buf.readableBytes());
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
assertEquals(bytes[i], buf.getByte(buf.readerIndex() + i));
|
||||
}
|
||||
ByteBuf buf = msg.content();
|
||||
assertEquals(bytes.length, buf.readableBytes());
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
assertEquals(bytes[i], buf.getByte(buf.readerIndex() + i));
|
||||
}
|
||||
|
||||
// Test that the channel's localAddress is equal to the message's recipient
|
||||
assertEquals(ctx.channel().localAddress(), msg.recipient());
|
||||
// Test that the channel's localAddress is equal to the message's recipient
|
||||
assertEquals(ctx.channel().localAddress(), msg.recipient());
|
||||
|
||||
if (echo) {
|
||||
ctx.writeAndFlush(new DatagramPacket(buf.retainedDuplicate(), msg.sender()));
|
||||
if (echo) {
|
||||
ctx.writeAndFlush(new DatagramPacket(buf.retainedDuplicate(), msg.sender()));
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
errorRef.compareAndSet(null, cause);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -331,4 +385,15 @@ public class DatagramUnicastTest extends AbstractDatagramTest {
|
||||
channel.close().sync();
|
||||
}
|
||||
}
|
||||
|
||||
protected InetSocketAddress sendToAddress(InetSocketAddress serverAddress) {
|
||||
InetAddress addr = serverAddress.getAddress();
|
||||
if (addr.isAnyLocalAddress()) {
|
||||
if (addr instanceof Inet6Address) {
|
||||
return new InetSocketAddress(NetUtil.LOCALHOST6, serverAddress.getPort());
|
||||
}
|
||||
return new InetSocketAddress(NetUtil.LOCALHOST4, serverAddress.getPort());
|
||||
}
|
||||
return serverAddress;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
package io.netty.channel.epoll;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.testsuite.transport.TestsuitePermutation.BootstrapComboFactory;
|
||||
import io.netty.testsuite.transport.socket.DatagramUnicastIPv6MappedTest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EpollDatagramUnicastIPv6MappedTest extends DatagramUnicastIPv6MappedTest {
|
||||
@Override
|
||||
protected List<BootstrapComboFactory<Bootstrap, Bootstrap>> newFactories() {
|
||||
return EpollSocketTestPermutation.INSTANCE.datagram(internetProtocolFamily());
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
package io.netty.channel.epoll;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.testsuite.transport.TestsuitePermutation;
|
||||
import io.netty.testsuite.transport.socket.DatagramUnicastIPv6Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EpollDatagramUnicastIPv6Test extends DatagramUnicastIPv6Test {
|
||||
@Override
|
||||
protected List<TestsuitePermutation.BootstrapComboFactory<Bootstrap, Bootstrap>> newFactories() {
|
||||
return EpollSocketTestPermutation.INSTANCE.datagram(internetProtocolFamily());
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
package io.netty.channel.kqueue;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.testsuite.transport.TestsuitePermutation.BootstrapComboFactory;
|
||||
import io.netty.testsuite.transport.socket.DatagramUnicastIPv6MappedTest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class KQueueDatagramUnicastIPv6MappedTest extends DatagramUnicastIPv6MappedTest {
|
||||
@Override
|
||||
protected List<BootstrapComboFactory<Bootstrap, Bootstrap>> newFactories() {
|
||||
return KQueueSocketTestPermutation.INSTANCE.datagram(internetProtocolFamily());
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
package io.netty.channel.kqueue;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.socket.InternetProtocolFamily;
|
||||
import io.netty.testsuite.transport.TestsuitePermutation;
|
||||
import io.netty.testsuite.transport.socket.DatagramUnicastIPv6Test;
|
||||
import io.netty.testsuite.transport.socket.DatagramUnicastTest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class KQueueDatagramUnicastIPv6Test extends DatagramUnicastIPv6Test {
|
||||
@Override
|
||||
protected List<TestsuitePermutation.BootstrapComboFactory<Bootstrap, Bootstrap>> newFactories() {
|
||||
return KQueueSocketTestPermutation.INSTANCE.datagram(internetProtocolFamily());
|
||||
}
|
||||
}
|
@ -103,7 +103,8 @@ static jobject createDatagramSocketAddress(JNIEnv* env, const struct sockaddr_st
|
||||
} else {
|
||||
offset = 0;
|
||||
}
|
||||
(*env)->SetByteArrayRegion(env, addressBytes, offset, ipLength, (jbyte*) &s->sin6_addr.s6_addr);
|
||||
jbyte* addr = (jbyte*) &s->sin6_addr.s6_addr;
|
||||
(*env)->SetByteArrayRegion(env, addressBytes, 0, ipLength, addr + offset);
|
||||
}
|
||||
jobject obj = (*env)->NewObject(env, datagramSocketAddressClass, datagramSocketAddrMethodId, addressBytes, scopeId, port, len, local);
|
||||
if ((*env)->ExceptionCheck(env) == JNI_TRUE) {
|
||||
|
Loading…
Reference in New Issue
Block a user