5 min read

<Java> DNS Cache TTL 설정 (System vs Security)

DNS cache TTL?

흔히 DNS LookUp을 통해 특정 도메인에 해당하는 IP 주소 값을 얻어옵니다. Java의 경우 특이하게 JVM 단에서 DNS에 대한 IP 정보를 캐싱해서 매번 hosts로부터 Lookup하지 않고 값을 가져올 수 있도록 합니다. 여기서 JVM에 저장되는 DNS 캐싱 시 얼마만큼의 주기만큼 캐싱 할지를 정할 수 있는 값이 DNS Cache TTL 입니다.

설정하는 방법들

JVM 전역 설정

Java 8 버전에서는 $JAVA_HOME/jre/lib/security/java.security 를 8 버전 이상에서는 $JAVA_HOME/conf/security/java.security 파일을 수정해주면 됩니다. 해당 파일 내에서 아래와 같이 원하는 값을 설정할 수 있습니다.

#
# This is the "master security properties file".
#
# An alternate java.security properties file may be specified
...
# 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
...
networkaddress.cache.ttl=5
... 

구동되는 java process 마다 별도로 설정

  1. Security 프로퍼티 설정
    Security.setProperty("networkaddress.cache.ttl", "5");
  2. System 프로퍼티 설정
    System.setProperty("sun.net.inetaddr.ttl", "5");

위와 같이 로직 내에서 DNS 캐싱 값을 설정할 수 있다. 단순히 위와 같이 설정한다라는 글을 구글링해도 많은데 둘 중의 어떤 설정값이 우선순위를 가지는지에 대한 실험은 없었다. 이에 대한 실험을 테스트 코드를 통해 알아보자.

우선순위 확인

아래와 같이 간단하게 테스트 코드를 작성할 수 있다. 간략히 설명하면 내가 설정한 도메인에 해당하는 IP 주소를 1초 단위로 출력해주는 테스트 코드이다. 만약에 System 프로퍼티에 의한 값이 우선순위가 높다면 hosts 정보를 변경하고 나서 10초 뒤에 캐싱이 될 것이고 Security 값이 우선순위가 높다면 설정한 0초, 즉 곧바로 변경되어야 할 것이다.

public class Main {
  private static final String TEST_DOMAIN = "brido.me";

  public static void main(String[] args) throws InterruptedException {

      System.setProperty("sun.net.inetaddr.ttl", "10");
	  Security.setProperty("networkaddress.cache.ttl", "0");

    for (int i = 0; i < 1000; i++) {
      try {
        InetAddress address = InetAddress.getByName(TEST_DOMAIN);
        System.out.println(getCurrentTime() + " lookup success " + address);
      } catch (Exception ignore) {
        System.out.println(getCurrentTime() + " lookup failed");
      } finally {
        Thread.sleep(1000);
      }
    }
  }

  private static String getCurrentTime() {
    DateFormat dateFormat = new SimpleDateFormat("mm:ss");
    return dateFormat.format(Calendar.getInstance().getTime());
  }
}

자바단에서의 코드 작성을 하고 나서 로컬 장비의 hosts 정보를 수정해야한다. macOS의 경우 간단하게 vi /etc/hosts를 통해 자신이 원하는 도메인과 IP를 넣어주면 된다. 이후 메인 스레드를 동작 시킨 뒤, 호스트 정보에서 내가 설정한 도메인에 해당하는 IP 주소를 변경해주면 실험 결과를 확인해볼 수 있다.

41분 05초 쯤에 터미널을 통해 직접 호스트 정보를 변경하였고 이는 41분 07초에 곧바로 반영되어 변경된 정보가 출력되는 것을 확인할 수 있다. 결론적으로 Security 프로퍼티 설정이 우선 순위를 가진다는 것을 확인 가능하다.

실험 환경 : macOS Sonoma, OpenJDK 1.8,11,17

InetAddressCachePolicy

Java의 DNS 캐싱을 알아보면 위 클래스가 항상 빠지지 않고 언급된다. 이 클래스와 관련 있는 SecurityManager의 경우 Java 17 버전 이후에서는 클래스의 용도가 불분명해지면서 deprecated되었다. 이로 인해 SecurityManager를 내부적으로 사용하는 InetAddressCachePolicy는 Java17 이후 버전에서 클래스로 접근을 하려면 IllegalAccessException이 발생한다.

아래를 통해 해당 InetAddressCachePolicy 로직을 잠깐 살펴보자. AccessController의 스태틱 메서드를 통해 Security 프로퍼티로부터 먼저 설정 값을 확인하고 있으면 리턴을 하고 없으면 System 프로퍼티로부터 값을 확인한다. 이후 둘 다 없어서 tmp 변수가 null인 경우엔 SecurityManager 객체의 여부를 확인 후 null인 경우 디폴트인 30초로 설정한다.

로직의 경우에서도 Security 프로퍼티가 우선순위가 높다는 것을 대략 유추해볼 수 있다. 내부적으로 Security로부터 값을 정상적으로 파싱할 경우 곧바로 결과를 반환한다.즉, Security 프로퍼티로부터 값을 가져올 경우 System 프로퍼티를 확인하지 않고 있다. 그리고 tmp 값이 null이고 SecurityManager 또한 설정되지 않았을 경우엔 DEFAULT_POSITIVE인 30초로 그 값이 설정되게 된다.

    /*
     * Initialize
     */
    static {
        Integer tmp = java.security.AccessController.doPrivileged(
          new PrivilegedAction<Integer>() {
            public Integer run() {
                try {
                    String tmpString = Security.getProperty(cachePolicyProp);
                    if (tmpString != null) {
                        return Integer.valueOf(tmpString);
                    }
                } catch (NumberFormatException ignored) {
                    // Ignore
                }

                try {
                    String tmpString = System.getProperty(cachePolicyPropFallback);
                    if (tmpString != null) {
                        return Integer.decode(tmpString);
                    }
                } catch (NumberFormatException ignored) {
                    // Ignore
                }
                return null;
            }
          });

        if (tmp != null) {
            cachePolicy = tmp < 0 ? FOREVER : tmp;
            propertySet = true;
        } else {
            /* No properties defined for positive caching. If there is no
             * security manager then use the default positive cache value.
             */
            if (System.getSecurityManager() == null) {
                cachePolicy = DEFAULT_POSITIVE;
            }
        }

정리해보면 앞서 살펴본 방식들로 위에 해당하는 값들을 직접 조절할 수 있다. 또한 로직상에서의 SecurityManager는 java17 이후에서는 deprecated 되었기에 java17 버전 이후에서는 InetAddressCachePolicy 클래스를 직접 활용할 수 없다.