Fix native image build on modern GraalVM versions for the cases when the program uses netty-dns (#10630)

Motivation:

Since GraalVM version 19.3.0, instances of java.net.InetAddress (and its subclasses Inet4Address and Inet6Address) are not allowed in native image heap (that is, they cannot be stored in static fields of classes initialized at build time or be reachable through static fields of such classes). When building a native image, it makes sense to initialize at build time as many classes as possible.
But some fields of some classes in Netty (for example, NetUtil.LOCALHOST4) contain InetAddress instances. If a program is using code path that makes it possible to reach such fields at build time initialization, it becomes impossible to build a native image initializing core Netty classes initialized at runtime. An example of such a program is a client that uses netty-dns.

Modifications:

- Add netty-testsuite-native-image-client Maven module to test that such an example program can be built after the corresponding fixes
- Add native-image.properties to resolver-dns module to move initialization of some classes to runtime (some of them are parsing configuration during initialization, so it makes no sense to initialize them at build time; for others, it's needed to avoid InetAddress reachability at build time)
- Add substitutions for NetUtil.LOCALHOST4, NetUtil.LOCALHOST6 and NetUtil.LOCALHOST to overcome the InetAddress-related prohibition
- Extract some initialization code from NetUtil to NetUtilInitializations to allow it to be used by the substitutions

Result:

A client program using netty-dns with --initialize-at-build-time=io.netty builds successfully
This commit is contained in:
Roman Puchkovskiy 2020-10-26 11:34:31 +04:00 committed by Norman Maurer
parent 5ad80c5887
commit dba46aa3da
12 changed files with 509 additions and 115 deletions

View File

@ -15,8 +15,8 @@
*/
package io.netty.util;
import io.netty.util.NetUtilInitializations.NetworkIfaceAndInetAddress;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SocketUtils;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger;
@ -33,13 +33,9 @@ import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import static io.netty.util.AsciiString.indexOf;
@ -139,113 +135,15 @@ public final class NetUtil {
logger.debug("-Djava.net.preferIPv4Stack: {}", IPV4_PREFERRED);
logger.debug("-Djava.net.preferIPv6Addresses: {}", IPV6_ADDRESSES_PREFERRED);
byte[] LOCALHOST4_BYTES = {127, 0, 0, 1};
byte[] LOCALHOST6_BYTES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
// Create IPv4 loopback address.
Inet4Address localhost4;
try {
localhost4 = (Inet4Address) InetAddress.getByAddress("localhost", LOCALHOST4_BYTES);
} catch (UnknownHostException e) {
// We should not get here as long as the length of the address is correct.
throw new ExceptionInInitializerError(e);
}
LOCALHOST4 = localhost4;
LOCALHOST4 = NetUtilInitializations.createLocalhost4();
// Create IPv6 loopback address.
Inet6Address localhost6;
try {
localhost6 = (Inet6Address) InetAddress.getByAddress("localhost", LOCALHOST6_BYTES);
} catch (Exception e) {
// We should not get here as long as the length of the address is correct.
throw new ExceptionInInitializerError(e);
}
LOCALHOST6 = localhost6;
LOCALHOST6 = NetUtilInitializations.createLocalhost6();
// Retrieve the list of available network interfaces.
List<NetworkInterface> ifaces = new ArrayList<>();
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
if (interfaces != null) {
while (interfaces.hasMoreElements()) {
NetworkInterface iface = interfaces.nextElement();
// Use the interface with proper INET addresses only.
if (SocketUtils.addressesFromNetworkInterface(iface).hasMoreElements()) {
ifaces.add(iface);
}
}
}
} catch (SocketException e) {
logger.warn("Failed to retrieve the list of available network interfaces", e);
}
// Find the first loopback interface available from its INET address (127.0.0.1 or ::1)
// Note that we do not use NetworkInterface.isLoopback() in the first place because it takes long time
// on a certain environment. (e.g. Windows with -Djava.net.preferIPv4Stack=true)
NetworkInterface loopbackIface = null;
InetAddress loopbackAddr = null;
loop: for (NetworkInterface iface: ifaces) {
for (Enumeration<InetAddress> i = SocketUtils.addressesFromNetworkInterface(iface); i.hasMoreElements();) {
InetAddress addr = i.nextElement();
if (addr.isLoopbackAddress()) {
// Found
loopbackIface = iface;
loopbackAddr = addr;
break loop;
}
}
}
// If failed to find the loopback interface from its INET address, fall back to isLoopback().
if (loopbackIface == null) {
try {
for (NetworkInterface iface: ifaces) {
if (iface.isLoopback()) {
Enumeration<InetAddress> i = SocketUtils.addressesFromNetworkInterface(iface);
if (i.hasMoreElements()) {
// Found the one with INET address.
loopbackIface = iface;
loopbackAddr = i.nextElement();
break;
}
}
}
if (loopbackIface == null) {
logger.warn("Failed to find the loopback interface");
}
} catch (SocketException e) {
logger.warn("Failed to find the loopback interface", e);
}
}
if (loopbackIface != null) {
// Found the loopback interface with an INET address.
logger.debug(
"Loopback interface: {} ({}, {})",
loopbackIface.getName(), loopbackIface.getDisplayName(), loopbackAddr.getHostAddress());
} else {
// Could not find the loopback interface, but we can't leave LOCALHOST as null.
// Use LOCALHOST6 or LOCALHOST4, preferably the IPv6 one.
if (loopbackAddr == null) {
try {
if (NetworkInterface.getByInetAddress(LOCALHOST6) != null) {
logger.debug("Using hard-coded IPv6 localhost address: {}", localhost6);
loopbackAddr = localhost6;
}
} catch (Exception e) {
// Ignore
} finally {
if (loopbackAddr == null) {
logger.debug("Using hard-coded IPv4 localhost address: {}", localhost4);
loopbackAddr = localhost4;
}
}
}
}
LOOPBACK_IF = loopbackIface;
LOCALHOST = loopbackAddr;
NetworkIfaceAndInetAddress loopback = NetUtilInitializations.determineLoopback(LOCALHOST4, LOCALHOST6);
LOOPBACK_IF = loopback.iface();
LOCALHOST = loopback.address();
// As a SecurityManager may prevent reading the somaxconn file we wrap this in a privileged block.
//

View File

@ -0,0 +1,172 @@
/*
* 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.
*/
package io.netty.util;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SocketUtils;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
final class NetUtilInitializations {
/**
* The logger being used by this class
*/
private static final InternalLogger logger = InternalLoggerFactory.getInstance(NetUtilInitializations.class);
private NetUtilInitializations() {
}
static Inet4Address createLocalhost4() {
byte[] LOCALHOST4_BYTES = {127, 0, 0, 1};
Inet4Address localhost4 = null;
try {
localhost4 = (Inet4Address) InetAddress.getByAddress("localhost", LOCALHOST4_BYTES);
} catch (Exception e) {
// We should not get here as long as the length of the address is correct.
PlatformDependent.throwException(e);
}
return localhost4;
}
static Inet6Address createLocalhost6() {
byte[] LOCALHOST6_BYTES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
Inet6Address localhost6 = null;
try {
localhost6 = (Inet6Address) InetAddress.getByAddress("localhost", LOCALHOST6_BYTES);
} catch (Exception e) {
// We should not get here as long as the length of the address is correct.
PlatformDependent.throwException(e);
}
return localhost6;
}
static NetworkIfaceAndInetAddress determineLoopback(Inet4Address localhost4, Inet6Address localhost6) {
// Retrieve the list of available network interfaces.
List<NetworkInterface> ifaces = new ArrayList<>();
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
if (interfaces != null) {
while (interfaces.hasMoreElements()) {
NetworkInterface iface = interfaces.nextElement();
// Use the interface with proper INET addresses only.
if (SocketUtils.addressesFromNetworkInterface(iface).hasMoreElements()) {
ifaces.add(iface);
}
}
}
} catch (SocketException e) {
logger.warn("Failed to retrieve the list of available network interfaces", e);
}
// Find the first loopback interface available from its INET address (127.0.0.1 or ::1)
// Note that we do not use NetworkInterface.isLoopback() in the first place because it takes long time
// on a certain environment. (e.g. Windows with -Djava.net.preferIPv4Stack=true)
NetworkInterface loopbackIface = null;
InetAddress loopbackAddr = null;
loop: for (NetworkInterface iface: ifaces) {
for (Enumeration<InetAddress> i = SocketUtils.addressesFromNetworkInterface(iface); i.hasMoreElements();) {
InetAddress addr = i.nextElement();
if (addr.isLoopbackAddress()) {
// Found
loopbackIface = iface;
loopbackAddr = addr;
break loop;
}
}
}
// If failed to find the loopback interface from its INET address, fall back to isLoopback().
if (loopbackIface == null) {
try {
for (NetworkInterface iface: ifaces) {
if (iface.isLoopback()) {
Enumeration<InetAddress> i = SocketUtils.addressesFromNetworkInterface(iface);
if (i.hasMoreElements()) {
// Found the one with INET address.
loopbackIface = iface;
loopbackAddr = i.nextElement();
break;
}
}
}
if (loopbackIface == null) {
logger.warn("Failed to find the loopback interface");
}
} catch (SocketException e) {
logger.warn("Failed to find the loopback interface", e);
}
}
if (loopbackIface != null) {
// Found the loopback interface with an INET address.
logger.debug(
"Loopback interface: {} ({}, {})",
loopbackIface.getName(), loopbackIface.getDisplayName(), loopbackAddr.getHostAddress());
} else {
// Could not find the loopback interface, but we can't leave LOCALHOST as null.
// Use LOCALHOST6 or LOCALHOST4, preferably the IPv6 one.
if (loopbackAddr == null) {
try {
if (NetworkInterface.getByInetAddress(localhost6) != null) {
logger.debug("Using hard-coded IPv6 localhost address: {}", localhost6);
loopbackAddr = localhost6;
}
} catch (Exception e) {
// Ignore
} finally {
if (loopbackAddr == null) {
logger.debug("Using hard-coded IPv4 localhost address: {}", localhost4);
loopbackAddr = localhost4;
}
}
}
}
return new NetworkIfaceAndInetAddress(loopbackIface, loopbackAddr);
}
static final class NetworkIfaceAndInetAddress {
private final NetworkInterface iface;
private final InetAddress address;
NetworkIfaceAndInetAddress(NetworkInterface iface, InetAddress address) {
this.iface = iface;
this.address = address;
}
public NetworkInterface iface() {
return iface;
}
public InetAddress address() {
return address;
}
}
}

View File

@ -0,0 +1,78 @@
/*
* 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.
*/
package io.netty.util;
import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.InjectAccessors;
import com.oracle.svm.core.annotate.TargetClass;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@TargetClass(NetUtil.class)
final class NetUtilSubstitutions {
private NetUtilSubstitutions() {
}
@Alias
@InjectAccessors(NetUtilLocalhost4Accessor.class)
public static Inet4Address LOCALHOST4;
@Alias
@InjectAccessors(NetUtilLocalhost6Accessor.class)
public static Inet6Address LOCALHOST6;
@Alias
@InjectAccessors(NetUtilLocalhostAccessor.class)
public static InetAddress LOCALHOST;
private static final class NetUtilLocalhost4Accessor {
static Inet4Address get() {
// using https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
return NetUtilLocalhost4LazyHolder.LOCALHOST4;
}
}
private static final class NetUtilLocalhost4LazyHolder {
private static final Inet4Address LOCALHOST4 = NetUtilInitializations.createLocalhost4();
}
private static final class NetUtilLocalhost6Accessor {
static Inet6Address get() {
// using https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
return NetUtilLocalhost6LazyHolder.LOCALHOST6;
}
}
private static final class NetUtilLocalhost6LazyHolder {
private static final Inet6Address LOCALHOST6 = NetUtilInitializations.createLocalhost6();
}
private static final class NetUtilLocalhostAccessor {
static InetAddress get() {
// using https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
return NetUtilLocalhostLazyHolder.LOCALHOST;
}
}
private static final class NetUtilLocalhostLazyHolder {
private static final InetAddress LOCALHOST = NetUtilInitializations
.determineLoopback(NetUtilLocalhost4LazyHolder.LOCALHOST4, NetUtilLocalhost6LazyHolder.LOCALHOST6)
.address();
}
}

View File

@ -12,4 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
Args = --initialize-at-run-time=io.netty.util.AbstractReferenceCounted,io.netty.util.concurrent.GlobalEventExecutor,io.netty.util.concurrent.ImmediateEventExecutor,io.netty.util.concurrent.ScheduledFutureTask
Args = --initialize-at-run-time=io.netty.util.AbstractReferenceCounted,io.netty.util.concurrent.GlobalEventExecutor,io.netty.util.concurrent.ImmediateEventExecutor,io.netty.util.concurrent.ScheduledFutureTask \
--initialize-at-run-time=io.netty.util.NetUtilSubstitutions$NetUtilLocalhost4LazyHolder \
--initialize-at-run-time=io.netty.util.NetUtilSubstitutions$NetUtilLocalhost6LazyHolder \
--initialize-at-run-time=io.netty.util.NetUtilSubstitutions$NetUtilLocalhostLazyHolder

View File

@ -394,6 +394,7 @@
<module>testsuite-osgi</module>
<module>testsuite-shading</module>
<module>testsuite-native-image</module>
<module>testsuite-native-image-client</module>
<module>transport-blockhound-tests</module>
<module>microbench</module>
<module>bom</module>

View File

@ -0,0 +1,18 @@
# 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.
Args = --initialize-at-run-time=io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider \
--initialize-at-run-time=io.netty.resolver.dns.DnsServerAddressStreamProviders$DefaultProviderHolder \
--initialize-at-run-time=io.netty.resolver.dns.DnsNameResolver \
--initialize-at-run-time=io.netty.resolver.HostsFileEntriesResolver

View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.netty</groupId>
<artifactId>netty-parent</artifactId>
<version>5.0.0.Final-SNAPSHOT</version>
</parent>
<artifactId>netty-testsuite-native-image-client</artifactId>
<packaging>jar</packaging>
<name>Netty/Testsuite/NativeImage/Client</name>
<properties>
<skipJapicmp>true</skipJapicmp>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-transport</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-resolver-dns</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>skipTests</id>
<activation>
<property>
<name>skipTests</name>
</property>
</activation>
<properties>
<skipNativeImageTestsuite>true</skipNativeImageTestsuite>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>com.oracle.substratevm</groupId>
<artifactId>native-image-maven-plugin</artifactId>
<version>${graalvm.version}</version>
<executions>
<execution>
<goals>
<goal>native-image</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<skip>${skipNativeImageTestsuite}</skip>
<imageName>${project.artifactId}</imageName>
<mainClass>io.netty.testsuite.svm.client.DnsNativeClient</mainClass>
<buildArgs>--report-unsupported-elements-at-runtime --allow-incomplete-classpath --no-fallback --initialize-at-build-time=io.netty -H:ReflectionConfigurationResources=reflection-config.json</buildArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<!-- This will do a whitesmoke test: if the substitutions are missing the binary will fail to run -->
<!-- If the metadata is missing the build above will fail -->
<execution>
<id>verify-native-image</id>
<phase>verify</phase>
<goals>
<goal>exec</goal>
</goals>
</execution>
</executions>
<configuration>
<skip>${skipNativeImageTestsuite}</skip>
<executable>${project.build.directory}/${project.artifactId}</executable>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,48 @@
/*
* 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.
*/
package io.netty.testsuite.svm.client;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.resolver.AddressResolver;
import io.netty.resolver.dns.DnsAddressResolverGroup;
import io.netty.resolver.dns.DnsServerAddressStreamProviders;
import io.netty.util.concurrent.DefaultThreadFactory;
import java.net.InetSocketAddress;
/**
* A client that uses netty-dns and gets compiled to a native image.
*/
public final class DnsNativeClient {
/**
* Main entry point (not instantiable)
*/
private DnsNativeClient() {
}
public static void main(String[] args) throws Exception {
NioEventLoopGroup group = new NioEventLoopGroup(1, new DefaultThreadFactory("netty"));
DnsAddressResolverGroup resolverGroup = new DnsAddressResolverGroup(NioDatagramChannel.class,
DnsServerAddressStreamProviders.platformDefault());
AddressResolver<InetSocketAddress> resolver = resolverGroup.getResolver(group.next());
System.out.println(resolver);
resolver.close();
group.shutdownGracefully().get();
}
}

View File

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* A hello world server that should be compiled to native.
*/
package io.netty.testsuite.svm.client;

View File

@ -0,0 +1,8 @@
[
{
"name": "io.netty.channel.socket.nio.NioDatagramChannel",
"methods": [
{ "name": "<init>", "parameterTypes": [] }
]
}
]

View File

@ -25,17 +25,15 @@ import java.net.InetAddress;
* Internet Protocol (IP) families used byte the {@link DatagramChannel}
*/
public enum InternetProtocolFamily {
IPv4(Inet4Address.class, 1, NetUtil.LOCALHOST4),
IPv6(Inet6Address.class, 2, NetUtil.LOCALHOST6);
IPv4(Inet4Address.class, 1),
IPv6(Inet6Address.class, 2);
private final Class<? extends InetAddress> addressType;
private final int addressNumber;
private final InetAddress localHost;
InternetProtocolFamily(Class<? extends InetAddress> addressType, int addressNumber, InetAddress localHost) {
InternetProtocolFamily(Class<? extends InetAddress> addressType, int addressNumber) {
this.addressType = addressType;
this.addressNumber = addressNumber;
this.localHost = localHost;
}
/**
@ -58,7 +56,14 @@ public enum InternetProtocolFamily {
* Returns the {@link InetAddress} that represent the {@code LOCALHOST} for the family.
*/
public InetAddress localhost() {
return localHost;
switch (this) {
case IPv4:
return NetUtil.LOCALHOST4;
case IPv6:
return NetUtil.LOCALHOST6;
default:
throw new IllegalStateException("Unsupported family " + this);
}
}
/**

View File

@ -0,0 +1,36 @@
/*
* 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.
*/
package io.netty.channel.socket;
import io.netty.util.NetUtil;
import org.junit.Test;
import java.net.InetAddress;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
public class InternetProtocolFamilyTest {
@Test
public void ipv4ShouldHaveLocalhostOfIpV4() {
assertThat(InternetProtocolFamily.IPv4.localhost(), is((InetAddress) NetUtil.LOCALHOST4));
}
@Test
public void ipv6ShouldHaveLocalhostOfIpV6() {
assertThat(InternetProtocolFamily.IPv6.localhost(), is((InetAddress) NetUtil.LOCALHOST6));
}
}