Java DNS查询内部实现

String dottedQuadIpAddress = InetAddress.getByName( "blog.arganzheng.me" ).getHostAddress();

或者反过来IP对应域名:

InetAddress[] addresses = InetAddress.getAllByName("8.8.8.8"); // ip or DNS name for (int i = 0; i < addresses.length; i++) { String hostname = addresses[i].getHostName(); System.out.println(hostname); }

输出:

google-public-dns-a.google.com

那么InetAddress是如何实现DNS解析的呢?让我们深入代码一步步挖掘下去:

import java.net.UnknownHostException; public class InetAddress extends java.net.InetAddress implements java.io.Serializable { public static InetAddress getByName(String host) throws UnknownHostException { return InetAddress.getAllByName(host)[0]; } public static InetAddress[] getAllByName(String host) throws UnknownHostException { return getAllByName(host, null); } private static InetAddress[] getAllByName(String host, InetAddress reqAddr) throws UnknownHostException { // ... 省略对于IPV6地址判断,HostName或者IP地址判断 // hostname, resolve it return getAllByName0(host, reqAddr, true); } private static InetAddress[] getAllByName0(String host, InetAddress reqAddr, boolean check) throws UnknownHostException { /* If it gets here it is presumed to be a hostname */ /* Cache.get can return: null, unknownAddress, or InetAddress[] */ /* make sure the connection to the host is allowed, before we * give out a hostname */ if (check) { // 安全检查 SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkConnect(host, -1); } } // 从DNS Cache中获取 InetAddress[] addresses = getCachedAddresses(host); /* If no entry in cache, then do the host lookup */ if (addresses == null) { addresses = getAddressesFromNameService(host, reqAddr); } if (addresses == unknown_array) throw new UnknownHostException(host); return addresses.clone(); }

这里需要注意,JVM会先查询DNS缓存。有一个问题:默认的缓存时间是永远!这个是JDK实现中比较坑的地方。

InetAddress Caching

The InetAddress class has a cache to store successful as well as unsuccessful host name resolutions. By default, when a security manager is installed, in order to protect against DNS spoofing attacks, the result of positive host name resolutions are cached forever. When a security manager is not installed, the default behavior is to cache entries for a finite (implementation dependent) period of time. The result of unsuccessful host name resolution is cached for a very short period of time (10 seconds) to improve performance.

If the default behavior is not desired, then a Java security property can be set to a different Time-to-live (TTL) value for positive caching. Likewise, a system admin can configure a different negative caching TTL value when needed.

Two Java security properties control the TTL values used for positive and negative host name resolution caching:

networkaddress.cache.ttl Indicates the caching policy for successful name lookups from the name service. The value is specified as as integer to indicate the number of seconds to cache the successful lookup. The default setting is to cache for an implementation specific period of time. A value of -1 indicates "cache forever".

networkaddress.cache.negative.ttl (default: 10) Indicates the caching policy for un-successful name lookups from the name service. The value is specified as as integer to indicate the number of seconds to cache the failure for un-successful lookups. A value of 0 indicates "never cache". A value of -1 indicates "cache forever".

如果Cache miss,那么就会调用配置的nameServices执行真正DNS查询:

private static InetAddress[] getAddressesFromNameService(String host, InetAddress reqAddr) throws UnknownHostException { InetAddress[] addresses = null; boolean success = false; UnknownHostException ex = null; // Check whether the host is in the lookupTable. // 1) If the host isn't in the lookupTable when // checkLookupTable() is called, checkLookupTable() // would add the host in the lookupTable and // return null. So we will do the lookup. // 2) If the host is in the lookupTable when // checkLookupTable() is called, the current thread // would be blocked until the host is removed // from the lookupTable. Then this thread // should try to look up the addressCache. // i) if it found the addresses in the // addressCache, checkLookupTable() would // return the addresses. // ii) if it didn't find the addresses in the // addressCache for any reason, // it should add the host in the // lookupTable and return null so the // following code would do a lookup itself. if ((addresses = checkLookupTable(host)) == null) { try { // This is the first thread which looks up the addresses // this host or the cache entry for this host has been // expired so this thread should do the lookup. /* * 这里可以看到nameServices是链状的,这是JDK7+的逻辑。 * 插入自定义nameservice的逻辑就在这里。 */ for (NameService nameService : nameServices) { try { /* * Do not put the call to lookup() inside the * constructor. if you do you will still be * allocating space when the lookup fails. */ addresses = nameService.lookupAllHostAddr(host); success = true; break; } catch (UnknownHostException uhe) { if (host.equalsIgnoreCase("localhost")) { InetAddress[] local = new InetAddress[] { impl.loopbackAddress() }; addresses = local; success = true; break; } else { addresses = unknown_array; success = false; ex = uhe; } } } // More to do? if (reqAddr != null && addresses.length > 1 && !addresses[0].equals(reqAddr)) { // Find it? int i = 1; for (; i < addresses.length; i++) { if (addresses[i].equals(reqAddr)) { break; } } // Rotate if (i < addresses.length) { InetAddress tmp, tmp2 = reqAddr; for (int j = 0; j < i; j++) { tmp = addresses[j]; addresses[j] = tmp2; tmp2 = tmp; } addresses[i] = tmp2; } } // Cache the address. cacheAddresses(host, addresses, success); if (!success && ex != null) throw ex; } finally { // Delete host from the lookupTable and notify // all threads waiting on the lookupTable monitor. updateLookupTable(host); } } return addresses; }

这里有一段非常关键的代码:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/2502f6543a00bb6143a79c23ee2f39fb.html