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:
parent
0e2f4adc9d
commit
9eb4f0ee85
@ -34,11 +34,11 @@ public abstract class AbstractSocketTest extends AbstractComboTestsuiteTest<Serv
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) {
|
||||
bootstrap.localAddress(newSocketAddress());
|
||||
bootstrap.option(ChannelOption.ALLOCATOR, allocator);
|
||||
bootstrap.childOption(ChannelOption.ALLOCATOR, allocator);
|
||||
bootstrap2.option(ChannelOption.ALLOCATOR, allocator);
|
||||
protected void configure(ServerBootstrap sb, Bootstrap cb, ByteBufAllocator allocator) {
|
||||
sb.localAddress(newSocketAddress());
|
||||
sb.option(ChannelOption.ALLOCATOR, allocator);
|
||||
sb.childOption(ChannelOption.ALLOCATOR, allocator);
|
||||
cb.option(ChannelOption.ALLOCATOR, allocator);
|
||||
}
|
||||
|
||||
protected SocketAddress newSocketAddress() {
|
||||
|
@ -39,7 +39,7 @@ class EpollDomainDatagramPathTest extends AbstractClientSocketTest {
|
||||
run(testInfo, bootstrap -> {
|
||||
try {
|
||||
bootstrap.handler(new ChannelHandlerAdapter() { })
|
||||
.connect(EpollSocketTestPermutation.newSocketAddress()).get();
|
||||
.connect(EpollSocketTestPermutation.newDomainSocketAddress()).get();
|
||||
fail("Expected FileNotFoundException");
|
||||
} catch (Exception e) {
|
||||
assertTrue(e.getCause() instanceof FileNotFoundException);
|
||||
@ -52,10 +52,10 @@ class EpollDomainDatagramPathTest extends AbstractClientSocketTest {
|
||||
run(testInfo, bootstrap -> {
|
||||
try {
|
||||
Channel ch = bootstrap.handler(new ChannelHandlerAdapter() { })
|
||||
.bind(EpollSocketTestPermutation.newSocketAddress()).get();
|
||||
.bind(EpollSocketTestPermutation.newDomainSocketAddress()).get();
|
||||
ch.writeAndFlush(new DomainDatagramPacket(
|
||||
Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII),
|
||||
EpollSocketTestPermutation.newSocketAddress())).sync();
|
||||
EpollSocketTestPermutation.newDomainSocketAddress())).sync();
|
||||
fail("Expected FileNotFoundException");
|
||||
} catch (Exception e) {
|
||||
assertTrue(e.getCause() instanceof FileNotFoundException);
|
||||
|
@ -76,7 +76,7 @@ class EpollDomainDatagramUnicastTest extends DatagramUnicastTest {
|
||||
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -26,7 +26,7 @@ import java.util.List;
|
||||
public class EpollDomainSocketDataReadInitialStateTest extends SocketDataReadInitialStateTest {
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -25,7 +25,7 @@ import java.util.List;
|
||||
public class EpollDomainSocketEchoTest extends EpollSocketEchoTest {
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -43,7 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
public class EpollDomainSocketFdTest extends AbstractSocketTest {
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -25,7 +25,7 @@ import java.util.List;
|
||||
public class EpollDomainSocketFileRegionTest extends EpollSocketFileRegionTest {
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -27,7 +27,7 @@ public class EpollDomainSocketFixedLengthEchoTest extends SocketFixedLengthEchoT
|
||||
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -27,7 +27,7 @@ public class EpollDomainSocketGatheringWriteTest extends SocketGatheringWriteTes
|
||||
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -26,7 +26,7 @@ import java.util.List;
|
||||
public class EpollDomainSocketObjectEchoTest extends SocketObjectEchoTest {
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -26,7 +26,7 @@ import java.util.List;
|
||||
public class EpollDomainSocketReuseFdTest extends AbstractSocketReuseFdTest {
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -35,7 +35,7 @@ public class EpollDomainSocketShutdownOutputByPeerTest extends AbstractSocketShu
|
||||
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -31,6 +31,6 @@ public class EpollDomainSocketSslClientRenegotiateTest extends SocketSslClientRe
|
||||
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import java.util.List;
|
||||
public class EpollDomainSocketSslEchoTest extends SocketSslEchoTest {
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -26,7 +26,7 @@ import java.util.List;
|
||||
public class EpollDomainSocketSslGreetingTest extends SocketSslGreetingTest {
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -26,7 +26,7 @@ import java.util.List;
|
||||
public class EpollDomainSocketStartTlsTest extends SocketStartTlsTest {
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -26,7 +26,7 @@ import java.util.List;
|
||||
public class EpollDomainSocketStringEchoTest extends SocketStringEchoTest {
|
||||
@Override
|
||||
protected SocketAddress newSocketAddress() {
|
||||
return EpollSocketTestPermutation.newSocketAddress();
|
||||
return EpollSocketTestPermutation.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -47,7 +47,7 @@ public class EpollSocketTest extends SocketTest<LinuxSocket> {
|
||||
LinuxSocket s2 = LinuxSocket.newSocketDomain();
|
||||
|
||||
try {
|
||||
DomainSocketAddress dsa = UnixTestUtils.newSocketAddress();
|
||||
DomainSocketAddress dsa = UnixTestUtils.newDomainSocketAddress();
|
||||
s1.bind(dsa);
|
||||
s1.listen(1);
|
||||
|
||||
|
@ -184,8 +184,8 @@ class EpollSocketTestPermutation extends SocketTestPermutation {
|
||||
);
|
||||
}
|
||||
|
||||
public static DomainSocketAddress newSocketAddress() {
|
||||
return UnixTestUtils.newSocketAddress();
|
||||
public static DomainSocketAddress newDomainSocketAddress() {
|
||||
return UnixTestUtils.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
public List<TestsuitePermutation.BootstrapComboFactory<Bootstrap, Bootstrap>> domainDatagram() {
|
||||
|
@ -125,8 +125,8 @@ final class BsdSocket extends Socket {
|
||||
sourcePort = 0;
|
||||
} else {
|
||||
InetAddress sourceInetAddress = source.getAddress();
|
||||
sourceIPv6 = sourceInetAddress instanceof Inet6Address;
|
||||
if (sourceIPv6) {
|
||||
sourceIPv6 = useIpv6(this, sourceInetAddress);
|
||||
if (sourceInetAddress instanceof Inet6Address) {
|
||||
sourceAddress = sourceInetAddress.getAddress();
|
||||
sourceScopeId = ((Inet6Address) sourceInetAddress).getScopeId();
|
||||
} else {
|
||||
@ -138,10 +138,10 @@ final class BsdSocket extends Socket {
|
||||
}
|
||||
|
||||
InetAddress destinationInetAddress = destination.getAddress();
|
||||
boolean destinationIPv6 = destinationInetAddress instanceof Inet6Address;
|
||||
boolean destinationIPv6 = useIpv6(this, destinationInetAddress);
|
||||
byte[] destinationAddress;
|
||||
int destinationScopeId;
|
||||
if (destinationIPv6) {
|
||||
if (destinationInetAddress instanceof Inet6Address) {
|
||||
destinationAddress = destinationInetAddress.getAddress();
|
||||
destinationScopeId = ((Inet6Address) destinationInetAddress).getScopeId();
|
||||
} else {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -40,7 +40,7 @@ public class KQueueSocketTest extends SocketTest<BsdSocket> {
|
||||
BsdSocket s2 = BsdSocket.newSocketDomain();
|
||||
|
||||
try {
|
||||
DomainSocketAddress dsa = UnixTestUtils.newSocketAddress();
|
||||
DomainSocketAddress dsa = UnixTestUtils.newDomainSocketAddress();
|
||||
s1.bind(dsa);
|
||||
s1.listen(1);
|
||||
|
||||
@ -61,7 +61,7 @@ public class KQueueSocketTest extends SocketTest<BsdSocket> {
|
||||
BsdSocket s2 = BsdSocket.newSocketDomain();
|
||||
|
||||
try {
|
||||
DomainSocketAddress dsa = UnixTestUtils.newSocketAddress();
|
||||
DomainSocketAddress dsa = UnixTestUtils.newDomainSocketAddress();
|
||||
s1.bind(dsa);
|
||||
s1.listen(1);
|
||||
|
||||
|
@ -54,7 +54,7 @@ class KQueueSocketTestPermutation extends SocketTestPermutation {
|
||||
public List<TestsuitePermutation.BootstrapComboFactory<ServerBootstrap, Bootstrap>> socket() {
|
||||
|
||||
List<TestsuitePermutation.BootstrapComboFactory<ServerBootstrap, Bootstrap>> list =
|
||||
combo(serverSocket(), clientSocket());
|
||||
combo(serverSocket(), clientSocketWithFastOpen());
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
public static DomainSocketAddress newSocketAddress() {
|
||||
return UnixTestUtils.newSocketAddress();
|
||||
return UnixTestUtils.newDomainSocketAddress();
|
||||
}
|
||||
|
||||
public List<TestsuitePermutation.BootstrapComboFactory<Bootstrap, Bootstrap>> domainDatagram() {
|
||||
|
@ -20,9 +20,26 @@ import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
import java.io.File;
|
||||
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 {
|
||||
private static final Object INET_LOOPBACK_UNAVAILABLE = new Object();
|
||||
private static volatile Object inetLoopbackCache;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #newDomainSocketAddress()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static DomainSocketAddress newSocketAddress() {
|
||||
return newDomainSocketAddress();
|
||||
}
|
||||
|
||||
public static DomainSocketAddress newDomainSocketAddress() {
|
||||
try {
|
||||
File file;
|
||||
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() { }
|
||||
}
|
||||
|
@ -59,7 +59,15 @@ public class Socket extends FileDescriptor {
|
||||
* Returns {@code true} if we should use IPv6 internally, {@code false} otherwise.
|
||||
*/
|
||||
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 {
|
||||
|
Loading…
Reference in New Issue
Block a user