18

java.net.InetAddress resolves hostnames using the local machine's default host-name resolver by default:

Host name-to-IP address resolution is accomplished through the use of a combination of local machine configuration information and network naming services such as the Domain Name System (DNS) and Network Information Service(NIS). The particular naming services(s) being used is by default the local machine configured one. For any host name, its corresponding IP address is returned. [source]

How can we configure this behavior without modifying the local machine's default hostname resolver?

For example, is there anyway to configure java.net.InetAddress such that it resolves host names through OpenDNS (208.67.222.222, 208.67.220.220) or Google Public DNS (2001:4860:4860::8888, 2001:4860:4860::8844)?

Or is the only solution to explicitly create DNS packet requests, send them to the servers through either java.net.DatagramSocket or java.net.Socket, and parse the responses?

Flow
  • 23,572
  • 15
  • 99
  • 156
Pacerier
  • 86,231
  • 106
  • 366
  • 634

5 Answers5

14

Java 9 removed this capability. You will need to use a third party DNS client library.

If you are using Java 8 or older you can do:

You can set the system property sun.net.spi.nameservice.nameservers as documented by this site.

Pace
  • 41,875
  • 13
  • 113
  • 156
  • 1
    As documented in the same site: "These properties may not be supported in future releases." Is there another way to achieve that? – bplpu Oct 27 '16 at 08:05
  • 2
    No. Not if you want to use `java.net.InetAddress`. If you're ok using a different mechanism then you could of course use a 3rd party DNS library ( e.g. [dnsjava](http://www.dnsjava.org/) ). The only real reason the properties might change is if Oracle overhauls the `java.net` implementation in some future version of Java. If this happens they will likely provide an a new solution to this problem at that time as well. – Pace Oct 27 '16 at 09:01
  • 1
    They overhauled `java.net` in Java 9, so the `sun.net` classes needed to pull this off no longer exist. – ndm13 Jan 09 '18 at 16:57
  • You are correct. You will need to use a third party library starting with Java 9. – Pace Jan 09 '18 at 19:10
11

with the following Interface and allowing access to java.net.* it is possible to use own DNS provider with JDK8 and JDK9. The new Provider is installed via "INameService.install(new MyNameService());"

public interface INameService extends InvocationHandler {
    public static void install(final INameService dns) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, ClassNotFoundException {
        final Class<?> inetAddressClass = InetAddress.class;
        Object neu;
        Field nameServiceField;
        try {
            final Class<?> iface = Class.forName("java.net.InetAddress$NameService");
            nameServiceField = inetAddressClass.getDeclaredField("nameService");
            neu = Proxy.newProxyInstance(iface.getClassLoader(), new Class<?>[] { iface }, dns);
        } catch(final ClassNotFoundException|NoSuchFieldException e) {
            nameServiceField = inetAddressClass.getDeclaredField("nameServices");
            final Class<?> iface = Class.forName("sun.net.spi.nameservice.NameService");
            neu = Arrays.asList(Proxy.newProxyInstance(iface.getClassLoader(), new Class<?>[] { iface }, dns));
        }
        nameServiceField.setAccessible(true);
        nameServiceField.set(inetAddressClass, neu);
    }

    /**
     * Lookup a host mapping by name. Retrieve the IP addresses associated with a host
     *
     * @param host the specified hostname
     * @return array of IP addresses for the requested host
     * @throws UnknownHostException  if no IP address for the {@code host} could be found
     */
    InetAddress[] lookupAllHostAddr(final String host) throws UnknownHostException;

    /**
     * Lookup the host corresponding to the IP address provided
     *
     * @param addr byte array representing an IP address
     * @return {@code String} representing the host name mapping
     * @throws UnknownHostException
     *             if no host found for the specified IP address
     */
    String getHostByAddr(final byte[] addr) throws UnknownHostException;

    @Override default public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        switch(method.getName()) {
        case "lookupAllHostAddr": return lookupAllHostAddr((String)args[0]);
        case "getHostByAddr"    : return getHostByAddr    ((byte[])args[0]);
        default                 :
            final StringBuilder o = new StringBuilder();
            o.append(method.getReturnType().getCanonicalName()+" "+method.getName()+"(");
            final Class<?>[] ps = method.getParameterTypes();
            for(int i=0;i<ps.length;++i) {
                if(i>0) o.append(", ");
                o.append(ps[i].getCanonicalName()).append(" p").append(i);
            }
            o.append(")");
            throw new UnsupportedOperationException(o.toString());
        }
    }
}
SkateScout
  • 815
  • 14
  • 24
  • This was workign great but I think doesn't work any longer with JDK 18. Any ideas how to achieve this in JDK 18? – Aaron Iba Aug 03 '22 at 17:09
  • Hi, under JDK 18 this can be done via InetAddressResolverProvider and InetAddressResolver and define the Implementation Class under META-INF/services/java.net.spi.InetAddressResolverProvider – SkateScout Aug 04 '22 at 20:56
11

Java provided a new system parameter jdk.net.hosts.file for adding customized DNS records, as specified in the source code

 * The HostsFileNameService provides host address mapping
 * by reading the entries in a hosts file, which is specified by
 * {@code jdk.net.hosts.file} system property
 *
 * <p>The file format is that which corresponds with the /etc/hosts file
 * IP Address host alias list.
 *
 * <p>When the file lookup is enabled it replaces the default NameService
 * implementation
 *
 * @since 9
 */
private static final class HostsFileNameService implements NameService {

Example: we can start a Java application with JVM Option

  • -Djdk.net.hosts.file=/path/to/alternative/hosts_file

Where hosts_file content could be:

127.0.0.1    myserver-a.local
10.0.5.10    myserver-b.local
192.168.0.1  myserver-c.local

It will do the DNS trick based on the hosts_file

Happy
  • 757
  • 9
  • 18
  • Can we do it programmatically? – Nathan B Jun 30 '22 at 04:48
  • @NathanB - For me, in current environment I use a shell script dynamically generate the hosts_file before starting the Java application. - I did not find the API to dynamically add the DNS record at runtime yet, unfortunately – Happy Jun 30 '22 at 06:30
  • Yes, I couldn't find either, and it's a shame. Java should add this functionality as it is essential, both for testing, e.g. multiple servers on different IP addresses and also it can save time to avoid getting to DNS, but rather directly reach to the right IP. – Nathan B Jul 03 '22 at 04:43
8

For Java versions up to 8, here is the code I wrote to hard-code a foo system name DNS resolution in Java for test cases to pass. Its avantage is to append your specific entrie(s) to default Java runtime DNS resolution.

I recommend not to run it in production. Forced reflective access and Java runtime non-public implementation classes are used !

private static final String FOO_IP = "10.10.8.111";

/** Fake "foo" DNS resolution */
@SuppressWarnings("restriction")
public static class MyHostNameService implements sun.net.spi.nameservice.NameService {
    @Override
    public InetAddress[] lookupAllHostAddr(String paramString) throws UnknownHostException {
        if ("foo".equals(paramString) || "foo.domain.tld".equals(paramString)) {
            final byte[] arrayOfByte = sun.net.util.IPAddressUtil.textToNumericFormatV4(FOO_IP);
            final InetAddress address = InetAddress.getByAddress(paramString, arrayOfByte);
            return new InetAddress[] { address };
        } else {
            throw new UnknownHostException();
        }
    }
    @Override
    public String getHostByAddr(byte[] paramArrayOfByte) throws UnknownHostException {
        throw new UnknownHostException();
    }
}

static {
    // Force to load fake hostname resolution for tests to pass
    try {
        List<sun.net.spi.nameservice.NameService> nameServices =
            (List<sun.net.spi.nameservice.NameService>)
            org.apache.commons.lang3.reflect.FieldUtils.readStaticField(InetAddress.class, "nameServices", true);
        nameServices.add(new MyHostNameService());
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
}

Hope this helps, but again, to be used with care !

Yves Martin
  • 10,217
  • 2
  • 38
  • 77
0

As Pace indicated, the ability to configure name resolution was removed from the JDK. However, in Java 18, it was replaced with the SPI java.net.spi.InetAddressResolverProvider.

Between Java 8 and Java 18, as the JDK Inet4AddressImpl.c eventually ends up calling getnameinfo, you can still intercept that call with LD_PRELOAD shadowing.

There are libraries which make this very straightforward, such as CWrap.

Mikael Gueck
  • 5,511
  • 1
  • 27
  • 25