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:
Norman Maurer 2019-09-11 20:30:28 +02:00
parent f26840478a
commit 19a12fba4c
8 changed files with 316 additions and 49 deletions

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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) {