AndroidのDNSキャッシュを見る


Pocket

Android端末のDNSキャッシュを表示するようなアプリを作ろうと思ってちょっと調べてみました。

結論から言うと、期待通りのDNSキャッシュを参照することはできないようでした。

端末レベルのDNSキャシュ

Android端末が参照した全てのDNSレコードのキャッシュを参照したかったのですが、どうやらAndroid(というかLinux)はOSレベルでのDNSキャッシュはデフォルトでは持たないみたいですね。

/procとか/etcとかそれっぽいファイルを探してみましたがなさそうだし・・・。

<参考>
http://android.stackexchange.com/questions/12962/how-can-i-flush-the-dns-cache
Android2.3系でDNSキャッシュをクリアする

rootを取ってDNSキャッシュサーバをインストールすれば可能なのかもしれませんが、今回はroot無しでやりたかったのでパス。

ただ、いろいろネットで検索している中でInetAddressがなんかDNSキャッシュを持っているみたいなので試してみました。

 

InetAddressのDNSキャッシュ

InetAddressのドキュメントを見てもそれっぽいメソッドがないのでソースを見てみると確かにキャッシュしてる。
#InetAddress.clearDnsCache()がドキュメントに載ってないのはなんでだろ?

・android-19/java/net/InetAddress.java

public class InetAddress implements Serializable {
  /** Our Java-side DNS cache. */
  private static final AddressCache addressCache = new AddressCache();

  略...
}

・android-19/java/net/AddressCache.java

class AddressCache {
    /**
     * When the cache contains more entries than this, we start dropping the oldest ones.
     * This should be a power of two to avoid wasted space in our custom map.
     */
    private static final int MAX_ENTRIES = 16;

    // The TTL for the Java-level cache is short, just 2s.
    private static final long TTL_NANOS = 2 * 1000000000L;

    // The actual cache.
    private final BasicLruCache<String, AddressCacheEntry> cache
            = new BasicLruCache<String, AddressCacheEntry>(MAX_ENTRIES);
    
    static class AddressCacheEntry {
        // Either an InetAddress[] for a positive entry,
        // or a String detail message for a negative entry.
        final Object value;

        /**
         * The absolute expiry time in nanoseconds. Nanoseconds from System.nanoTime is ideal
         * because -- unlike System.currentTimeMillis -- it can never go backwards.
         *
         * We don't need to worry about overflow with a TTL_NANOS of 2s.
         */
        final long expiryNanos;

        AddressCacheEntry(Object value) {
            this.value = value;
            this.expiryNanos = System.nanoTime() + TTL_NANOS;
        }
    }

    略・・・
    public void put(String hostname, InetAddress[] addresses) {
        cache.put(hostname, new AddressCacheEntry(addresses));
    }
}

libcore/util/BasicLruCache.java

public class BasicLruCache<K, V> {
  private final LinkedHashMap<K, V> map;
  private final int maxSize;

  略・・・

どうやらホスト名をKeyにしてキャッシュを保持しているようです。
しかもprivateで・・・。

ということで、リフレクションを使ってキャッシュの中身を覗いてみることにしました。

	private void showDNSCache() throws NoSuchFieldException, IllegalAccessException {
		TextView view = (TextView)findViewById(R.id.textView1);
		
		  Class<InetAddress> cInetAddress = InetAddress.class;
		  Field fAddressCache = cInetAddress.getDeclaredField("addressCache");
		  fAddressCache.setAccessible(true);
		  Object addressCache = fAddressCache.get(null);

		  Class cAddressCache = addressCache.getClass();
		  Field fcache = cAddressCache.getDeclaredField("cache");
		  fcache.setAccessible(true);
		  Object cache = fcache.get(addressCache);
		  
		  Class cBasicLruCache = cache.getClass();
		  Field fmap = cBasicLruCache.getDeclaredField("map");
		  fmap.setAccessible(true);
		  Map<String, Object> map = (Map<String, Object>)fmap.get(cache);

		  StringBuffer sb = new StringBuffer();
		  for (Map.Entry<String, Object> entry : map.entrySet()) {
			  Object value = entry.getValue();
			  
			  Class cAddressCacheEntry = value.getClass();
			  Field fvalue = cAddressCacheEntry.getDeclaredField("value");
			  fvalue.setAccessible(true);
			  InetAddress[] addresses = (InetAddress[])fvalue.get(value);
			  
			  sb.append(entry.getKey() + "\t");
			  for (InetAddress address : addresses) {
				 sb.append(address.getHostAddress() + " ");
			  }
			  sb.append("\n");
		  }
		  
		  view.setText(sb.toString());
	}

実行してみると確かにキャッシュされてました。

DNSキャッシュ

ただこのキャッシュはプロセス毎のキャッシュになります。

 

libcのDNSキャッシュ

libcでもキャッシュしているようですが今回は調べてません。

 

あとがき

アンドロイドでDNSキャッシュを見る方法を調べてみたけど、結局解らなかったな・・・。

サンプル

InetAddressを使用したサンプルは下記を参照。

https://github.com/workpiles/ShowDnsCacheSample
 
 

Leave a Comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です