Javaの名前解決では、以下のようにキャッシュを使っているので、OS(hosts)やDNSで名前解決を変更しても、すぐに反映されるわけではない。
networkaddress.cache.ttl
java.securityで指定して、ネーム・サービスからの名前の検索に成功した場合のキャッシング・ポリシーを示します。指定する値は、成功した検索結果をキャッシュする秒数を示す整数です。-1の値は、「ずっとキャッシュする」という意味です。デフォルトでは、セキュリティ・マネージャがインストールされている場合はずっとキャッシュし、セキュリティ・マネージャがインストールされていない場合は実装固有の期間キャッシュします。
上記ページに「セキュリティ・マネージャがインストールされていない場合は実装固有の期間キャッシュします。」とあるが、実際にどれくらい名前解決のキャッシュを保持するのか調べてみた。
手元にあるJava8の java.security ファイルを見てみる。
#
# The Java-level namelookup cache policy for successful lookups:
#
# any negative value: caching forever
# any positive value: the number of seconds to cache an address for
# zero: do not cache
#
# default value is forever (FOREVER). For security reasons, this
# caching is made forever when a security manager is set. When a security
# manager is not set, the default behavior in this implementation
# is to cache for ★30 seconds.
#
# NOTE: setting this to anything other than the default value can have
# serious security implications. Do not set it unless
# you are sure you are not exposed to DNS spoofing attack.
#
#networkaddress.cache.ttl=-1
上記によると、キャッシュの保持時間は30秒と読み取れる。念のため、少し実機検証してみる。
実機検証に使ったプログラムは以下。
import java.net.InetAddress; import java.net.UnknownHostException; public class NameResolveSample { public static void main(String[] args) { String hostname = "www.test.local"; String ipAddress = "192.168.100.10"; int timeToWait = 35*1000; // ms try { String result1 = getIPaddrFromHostname(hostname); System.out.println("Hostname to ip address: " + result1); String result2 = getHostnameFromIPaddr(ipAddress); System.out.println("IP address to hostname: " + result2); // Change name resolution for test. // 192.168.100.10 www.test.local // -> 192.168.100.10 www.test1.local System.out.println("Waiting " + timeToWait/1000 + " seconds. Change name resolution(in hosts etc...) during this period."); Thread.sleep(timeToWait); String result3 = getIPaddrFromHostname(hostname); System.out.println("Hostname to ip address: " + result3); String result4 = getHostnameFromIPaddr(ipAddress); System.out.println("IP address to hostname: " + result4); } catch (Exception ex){ System.out.println(ex.getMessage()); System.out.println(ex.getStackTrace()); } } private static String getIPaddrFromHostname (String hostname) { InetAddress ia; try { ia = InetAddress.getByName(hostname); } catch (UnknownHostException ex) { System.out.println("Can't resolve " + hostname); return hostname; } byte[] octets = ia.getAddress(); StringBuffer ipStr = new StringBuffer(); for (byte octet : octets) { ipStr.append(Integer.toString(octet & 0xff)); // https://akrad.hatenablog.com/entry/2018/04/09/222238 ipStr.append('.'); } return stripEnd(ipStr.toString()); } private static String stripEnd (String str) { if (str != null && str.length() > 0) { str = str.substring(0, str.length()-1); } return str; } private static String getHostnameFromIPaddr (String ipaddr) { InetAddress ia; try { ia = InetAddress.getByName(ipaddr); } catch (UnknownHostException ex) { System.out.println("Can't resolve " + ipaddr); return ipaddr; } String fqdn = ia.getCanonicalHostName(); return fqdn; } }
これを timeToWait を変更しながら実行してみたところ、以下の結果になった。
〇1回目の名前解決から2回目の名前解決まで25秒(30秒以内)。
Hostname to ip address: 192.168.100.10
IP address to hostname: www.test.local
Waiting 25 seconds. Change name resolution during this period.
Hostname to ip address: 192.168.100.10
IP address to hostname: www.test1.local
〇1回目の名前解決から2回目の名前解決まで35秒(30秒以上)。
Hostname to ip address: 192.168.100.10
IP address to hostname: www.test.local
Waiting 35 seconds. Change name resolution during this period.
Can't resolve www.test.local
Hostname to ip address: www.test.local
IP address to hostname: www.test1.local
名前解決(正引き)の方は、予想通り、30秒以内であればキャッシュの結果が返るようだ。一方で、名前解決(逆引き)の方は、その都度の結果が返っており、キャッシュが使用されていないように見える。
この辺りは、今後 InetAddress クラスのソースを読んで仕様を確かめてみたい。