Fix a problem with IP protocol version confusion on MacOS when TCP FastOpen is enabled (#11588)

Motivation:
This fixes a bug that would result in an `io.netty.channel.unix.Errors$NativeIoException: connectx(..) failed: Address family not supported by protocol family` error.
This happens when the connecting socket is configured to use IPv6 but the address being connected to is IPv4.
This can occur because, for instance, Netty and `InetAddress.getLoopbackAddress()` have different preferences for IPv6 vs. IPv4.

Modification:
Pass the correct ipv6 or ipv4 flags to connectx, depending on whether the socket was created for AF_INET or AF_INET6, rather than relying on the IP version of the destination address.

Result:
No more issue with TCP FastOpen on MacOS when using addresses of the "wrong" IP version.
This commit is contained in:
Chris Vest 2021-08-18 20:43:01 +02:00
parent 0e2f4adc9d
commit 9eb4f0ee85
26 changed files with 140 additions and 35 deletions

View File

@ -34,11 +34,11 @@ public abstract class AbstractSocketTest extends AbstractComboTestsuiteTest<Serv
} }
@Override @Override
protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { protected void configure(ServerBootstrap sb, Bootstrap cb, ByteBufAllocator allocator) {
bootstrap.localAddress(newSocketAddress()); sb.localAddress(newSocketAddress());
bootstrap.option(ChannelOption.ALLOCATOR, allocator); sb.option(ChannelOption.ALLOCATOR, allocator);
bootstrap.childOption(ChannelOption.ALLOCATOR, allocator); sb.childOption(ChannelOption.ALLOCATOR, allocator);
bootstrap2.option(ChannelOption.ALLOCATOR, allocator); cb.option(ChannelOption.ALLOCATOR, allocator);
} }
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {

View File

@ -39,7 +39,7 @@ class EpollDomainDatagramPathTest extends AbstractClientSocketTest {
run(testInfo, bootstrap -> { run(testInfo, bootstrap -> {
try { try {
bootstrap.handler(new ChannelHandlerAdapter() { }) bootstrap.handler(new ChannelHandlerAdapter() { })
.connect(EpollSocketTestPermutation.newSocketAddress()).get(); .connect(EpollSocketTestPermutation.newDomainSocketAddress()).get();
fail("Expected FileNotFoundException"); fail("Expected FileNotFoundException");
} catch (Exception e) { } catch (Exception e) {
assertTrue(e.getCause() instanceof FileNotFoundException); assertTrue(e.getCause() instanceof FileNotFoundException);
@ -52,10 +52,10 @@ class EpollDomainDatagramPathTest extends AbstractClientSocketTest {
run(testInfo, bootstrap -> { run(testInfo, bootstrap -> {
try { try {
Channel ch = bootstrap.handler(new ChannelHandlerAdapter() { }) Channel ch = bootstrap.handler(new ChannelHandlerAdapter() { })
.bind(EpollSocketTestPermutation.newSocketAddress()).get(); .bind(EpollSocketTestPermutation.newDomainSocketAddress()).get();
ch.writeAndFlush(new DomainDatagramPacket( ch.writeAndFlush(new DomainDatagramPacket(
Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII), Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII),
EpollSocketTestPermutation.newSocketAddress())).sync(); EpollSocketTestPermutation.newDomainSocketAddress())).sync();
fail("Expected FileNotFoundException"); fail("Expected FileNotFoundException");
} catch (Exception e) { } catch (Exception e) {
assertTrue(e.getCause() instanceof FileNotFoundException); assertTrue(e.getCause() instanceof FileNotFoundException);

View File

@ -76,7 +76,7 @@ class EpollDomainDatagramUnicastTest extends DatagramUnicastTest {
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -26,7 +26,7 @@ import java.util.List;
public class EpollDomainSocketDataReadInitialStateTest extends SocketDataReadInitialStateTest { public class EpollDomainSocketDataReadInitialStateTest extends SocketDataReadInitialStateTest {
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -25,7 +25,7 @@ import java.util.List;
public class EpollDomainSocketEchoTest extends EpollSocketEchoTest { public class EpollDomainSocketEchoTest extends EpollSocketEchoTest {
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -43,7 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class EpollDomainSocketFdTest extends AbstractSocketTest { public class EpollDomainSocketFdTest extends AbstractSocketTest {
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -25,7 +25,7 @@ import java.util.List;
public class EpollDomainSocketFileRegionTest extends EpollSocketFileRegionTest { public class EpollDomainSocketFileRegionTest extends EpollSocketFileRegionTest {
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -27,7 +27,7 @@ public class EpollDomainSocketFixedLengthEchoTest extends SocketFixedLengthEchoT
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -27,7 +27,7 @@ public class EpollDomainSocketGatheringWriteTest extends SocketGatheringWriteTes
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -26,7 +26,7 @@ import java.util.List;
public class EpollDomainSocketObjectEchoTest extends SocketObjectEchoTest { public class EpollDomainSocketObjectEchoTest extends SocketObjectEchoTest {
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -26,7 +26,7 @@ import java.util.List;
public class EpollDomainSocketReuseFdTest extends AbstractSocketReuseFdTest { public class EpollDomainSocketReuseFdTest extends AbstractSocketReuseFdTest {
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -35,7 +35,7 @@ public class EpollDomainSocketShutdownOutputByPeerTest extends AbstractSocketShu
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -31,6 +31,6 @@ public class EpollDomainSocketSslClientRenegotiateTest extends SocketSslClientRe
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
} }

View File

@ -26,7 +26,7 @@ import java.util.List;
public class EpollDomainSocketSslEchoTest extends SocketSslEchoTest { public class EpollDomainSocketSslEchoTest extends SocketSslEchoTest {
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -26,7 +26,7 @@ import java.util.List;
public class EpollDomainSocketSslGreetingTest extends SocketSslGreetingTest { public class EpollDomainSocketSslGreetingTest extends SocketSslGreetingTest {
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -26,7 +26,7 @@ import java.util.List;
public class EpollDomainSocketStartTlsTest extends SocketStartTlsTest { public class EpollDomainSocketStartTlsTest extends SocketStartTlsTest {
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -26,7 +26,7 @@ import java.util.List;
public class EpollDomainSocketStringEchoTest extends SocketStringEchoTest { public class EpollDomainSocketStringEchoTest extends SocketStringEchoTest {
@Override @Override
protected SocketAddress newSocketAddress() { protected SocketAddress newSocketAddress() {
return EpollSocketTestPermutation.newSocketAddress(); return EpollSocketTestPermutation.newDomainSocketAddress();
} }
@Override @Override

View File

@ -0,0 +1,27 @@
/*
* Copyright 2021 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 io.netty.channel.unix.tests.UnixTestUtils;
import java.net.SocketAddress;
public class EpollJdkLoopbackSocketSslEchoTest extends EpollSocketSslEchoTest {
@Override
protected SocketAddress newSocketAddress() {
return UnixTestUtils.newInetLoopbackSocketAddress();
}
}

View File

@ -47,7 +47,7 @@ public class EpollSocketTest extends SocketTest<LinuxSocket> {
LinuxSocket s2 = LinuxSocket.newSocketDomain(); LinuxSocket s2 = LinuxSocket.newSocketDomain();
try { try {
DomainSocketAddress dsa = UnixTestUtils.newSocketAddress(); DomainSocketAddress dsa = UnixTestUtils.newDomainSocketAddress();
s1.bind(dsa); s1.bind(dsa);
s1.listen(1); s1.listen(1);

View File

@ -184,8 +184,8 @@ class EpollSocketTestPermutation extends SocketTestPermutation {
); );
} }
public static DomainSocketAddress newSocketAddress() { public static DomainSocketAddress newDomainSocketAddress() {
return UnixTestUtils.newSocketAddress(); return UnixTestUtils.newDomainSocketAddress();
} }
public List<TestsuitePermutation.BootstrapComboFactory<Bootstrap, Bootstrap>> domainDatagram() { public List<TestsuitePermutation.BootstrapComboFactory<Bootstrap, Bootstrap>> domainDatagram() {

View File

@ -125,8 +125,8 @@ final class BsdSocket extends Socket {
sourcePort = 0; sourcePort = 0;
} else { } else {
InetAddress sourceInetAddress = source.getAddress(); InetAddress sourceInetAddress = source.getAddress();
sourceIPv6 = sourceInetAddress instanceof Inet6Address; sourceIPv6 = useIpv6(this, sourceInetAddress);
if (sourceIPv6) { if (sourceInetAddress instanceof Inet6Address) {
sourceAddress = sourceInetAddress.getAddress(); sourceAddress = sourceInetAddress.getAddress();
sourceScopeId = ((Inet6Address) sourceInetAddress).getScopeId(); sourceScopeId = ((Inet6Address) sourceInetAddress).getScopeId();
} else { } else {
@ -138,10 +138,10 @@ final class BsdSocket extends Socket {
} }
InetAddress destinationInetAddress = destination.getAddress(); InetAddress destinationInetAddress = destination.getAddress();
boolean destinationIPv6 = destinationInetAddress instanceof Inet6Address; boolean destinationIPv6 = useIpv6(this, destinationInetAddress);
byte[] destinationAddress; byte[] destinationAddress;
int destinationScopeId; int destinationScopeId;
if (destinationIPv6) { if (destinationInetAddress instanceof Inet6Address) {
destinationAddress = destinationInetAddress.getAddress(); destinationAddress = destinationInetAddress.getAddress();
destinationScopeId = ((Inet6Address) destinationInetAddress).getScopeId(); destinationScopeId = ((Inet6Address) destinationInetAddress).getScopeId();
} else { } else {

View File

@ -0,0 +1,27 @@
/*
* Copyright 2021 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 io.netty.channel.unix.tests.UnixTestUtils;
import java.net.SocketAddress;
public class KQueueJdkLoopbackSocketSslEchoTest extends KQueueSocketSslEchoTest {
@Override
protected SocketAddress newSocketAddress() {
return UnixTestUtils.newInetLoopbackSocketAddress();
}
}

View File

@ -40,7 +40,7 @@ public class KQueueSocketTest extends SocketTest<BsdSocket> {
BsdSocket s2 = BsdSocket.newSocketDomain(); BsdSocket s2 = BsdSocket.newSocketDomain();
try { try {
DomainSocketAddress dsa = UnixTestUtils.newSocketAddress(); DomainSocketAddress dsa = UnixTestUtils.newDomainSocketAddress();
s1.bind(dsa); s1.bind(dsa);
s1.listen(1); s1.listen(1);
@ -61,7 +61,7 @@ public class KQueueSocketTest extends SocketTest<BsdSocket> {
BsdSocket s2 = BsdSocket.newSocketDomain(); BsdSocket s2 = BsdSocket.newSocketDomain();
try { try {
DomainSocketAddress dsa = UnixTestUtils.newSocketAddress(); DomainSocketAddress dsa = UnixTestUtils.newDomainSocketAddress();
s1.bind(dsa); s1.bind(dsa);
s1.listen(1); s1.listen(1);

View File

@ -54,7 +54,7 @@ class KQueueSocketTestPermutation extends SocketTestPermutation {
public List<TestsuitePermutation.BootstrapComboFactory<ServerBootstrap, Bootstrap>> socket() { public List<TestsuitePermutation.BootstrapComboFactory<ServerBootstrap, Bootstrap>> socket() {
List<TestsuitePermutation.BootstrapComboFactory<ServerBootstrap, Bootstrap>> list = List<TestsuitePermutation.BootstrapComboFactory<ServerBootstrap, Bootstrap>> list =
combo(serverSocket(), clientSocket()); combo(serverSocket(), clientSocketWithFastOpen());
list.remove(list.size() - 1); // Exclude NIO x NIO test list.remove(list.size() - 1); // Exclude NIO x NIO test
@ -139,8 +139,9 @@ class KQueueSocketTestPermutation extends SocketTestPermutation {
() -> new Bootstrap().group(KQUEUE_WORKER_GROUP).channel(KQueueDatagramChannel.class) () -> new Bootstrap().group(KQUEUE_WORKER_GROUP).channel(KQueueDatagramChannel.class)
); );
} }
public static DomainSocketAddress newSocketAddress() { public static DomainSocketAddress newSocketAddress() {
return UnixTestUtils.newSocketAddress(); return UnixTestUtils.newDomainSocketAddress();
} }
public List<TestsuitePermutation.BootstrapComboFactory<Bootstrap, Bootstrap>> domainDatagram() { public List<TestsuitePermutation.BootstrapComboFactory<Bootstrap, Bootstrap>> domainDatagram() {

View File

@ -20,9 +20,26 @@ import io.netty.util.internal.PlatformDependent;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public final class UnixTestUtils { public final class UnixTestUtils {
private static final Object INET_LOOPBACK_UNAVAILABLE = new Object();
private static volatile Object inetLoopbackCache;
/**
* @deprecated Use {@link #newDomainSocketAddress()} instead.
*/
@Deprecated
public static DomainSocketAddress newSocketAddress() { public static DomainSocketAddress newSocketAddress() {
return newDomainSocketAddress();
}
public static DomainSocketAddress newDomainSocketAddress() {
try { try {
File file; File file;
do { do {
@ -37,5 +54,30 @@ public final class UnixTestUtils {
} }
} }
/**
* The JDK method may produce IPv4 loopback addresses where {@link io.netty.util.NetUtil#LOCALHOST} might be an
* IPv6 addresses.
* This difference can stress the system in different ways that are important to test.
*/
public static SocketAddress newInetLoopbackSocketAddress() {
Object loopback = inetLoopbackCache;
if (loopback == null) {
inetLoopbackCache = loopback = getLoopbackAddress();
}
assumeTrue(loopback != INET_LOOPBACK_UNAVAILABLE, "InetAddress.getLoopbackAddress() is not available");
return new InetSocketAddress((InetAddress) loopback, 0);
}
private static Object getLoopbackAddress() {
try {
Method method = InetAddress.class.getMethod("getLoopbackAddress");
return method.invoke(null);
} catch (Exception ignore) {
return INET_LOOPBACK_UNAVAILABLE;
}
}
private UnixTestUtils() { } private UnixTestUtils() { }
} }

View File

@ -59,7 +59,15 @@ public class Socket extends FileDescriptor {
* Returns {@code true} if we should use IPv6 internally, {@code false} otherwise. * Returns {@code true} if we should use IPv6 internally, {@code false} otherwise.
*/ */
private boolean useIpv6(InetAddress address) { private boolean useIpv6(InetAddress address) {
return ipv6 || address instanceof Inet6Address; return useIpv6(this, address);
}
/**
* Returns {@code true} if the given socket and address combination should use IPv6 internally,
* {@code false} otherwise.
*/
protected static boolean useIpv6(Socket socket, InetAddress address) {
return socket.ipv6 || address instanceof Inet6Address;
} }
public final void shutdown() throws IOException { public final void shutdown() throws IOException {