Comment faire en sorte que Java respecte le délai de mise en cache DNS?

101

Nous utilisons GSLB pour la géo-distribution et l'équilibrage de charge. Chaque service se voit attribuer un nom de domaine fixe. Grâce à une certaine magie DNS, le nom de domaine est résolu en une adresse IP la plus proche du serveur avec le moins de charge. Pour que l'équilibrage de charge fonctionne, le serveur d'applications doit respecter le TTL de la réponse DNS et résoudre à nouveau le nom de domaine lorsque le cache expire. Cependant, je ne pouvais pas trouver un moyen de le faire en Java.

L'application est en Java 5, fonctionnant sous Linux (Centos 5).

Codeur ZZ
la source

Réponses:

76

Selon la réponse de Byron, vous ne pouvez pas définir networkaddress.cache.ttlou en networkaddress.cache.negative.ttltant que propriétés système en utilisant l' -Dindicateur ou en appelant System.setPropertycar ce ne sont pas des propriétés système - ce sont des propriétés de sécurité .

Si vous souhaitez utiliser une propriété System pour déclencher ce comportement (afin que vous puissiez utiliser l' -Dindicateur ou l'appel System.setProperty), vous souhaiterez définir la propriété System suivante :

-Dsun.net.inetaddr.ttl=0

Cette propriété système activera l'effet souhaité.

Mais soyez conscient: si vous n'utilisez pas l' -Dindicateur lors du démarrage du processus JVM et choisissez de l'appeler à partir du code à la place:

java.security.Security.setProperty("networkaddress.cache.ttl" , "0")

Ce code doit s'exécuter avant que tout autre code de la JVM tente d'effectuer des opérations de mise en réseau.

Ceci est important car, par exemple, si vous Security.setPropertyappelez un fichier .war et déployez ce .war sur Tomcat, cela ne fonctionnera pas: Tomcat utilise la pile réseau Java pour s'initialiser beaucoup plus tôt que le code de votre .war ne sera exécuté. En raison de cette «condition de concurrence», il est généralement plus pratique d'utiliser l' -Dindicateur lors du démarrage du processus JVM.

Si vous n'utilisez pas -Dsun.net.inetaddr.ttl=0ou n'appelez pas Security.setProperty, vous devrez modifier $JRE_HOME/lib/security/java.securityet définir ces propriétés de sécurité dans ce fichier, par exemple

networkaddress.cache.ttl = 0
networkaddress.cache.negative.ttl = 0

Mais faites attention aux avertissements de sécurité dans les commentaires entourant ces propriétés. Ne le faites que si vous êtes raisonnablement sûr que vous n'êtes pas vulnérable aux attaques d'usurpation DNS .

Les Hazlewood
la source
2
Le FQN est java.security.Security(au moins en jdk7)
Pablo Fernandez
1
Juste un commentaire, ces avertissements de sécurité concernent principalement les gestionnaires de sécurité et le téléchargement à distance. Pour toute application serveur normale qui fait confiance au DNS dans une certaine mesure, la réduction du TTL est très bien. (Cependant, je ne pense pas que 0 soit un bon minimum et que la valeur par défaut de 30 s pour les gestionnaires non liés à la sécurité est très bien dans la plupart des cas).
eckes
3
La propriété système fonctionne-t-elle également avec OpenJDK ou est-elle spécifique à Oracle?
mhlz
67

Java a un comportement de mise en cache DNS très étrange. Votre meilleur pari est de désactiver la mise en cache DNS ou de la définir sur un nombre faible comme 5 secondes.

networkaddress.cache.ttl (par défaut: -1)
Indique la stratégie de mise en cache pour les recherches de noms réussies à partir du service de noms. La valeur est spécifiée sous forme d'entier pour indiquer le nombre de secondes de mise en cache de la recherche réussie. Une valeur de -1 indique "cache pour toujours".

networkaddress.cache.negative.ttl (par défaut: 10)
Indique la stratégie de mise en cache pour les recherches de noms infructueuses à partir du service de noms. La valeur est spécifiée sous la forme d'un entier pour indiquer le nombre de secondes de mise en cache de l'échec en cas d'échec des recherches. Une valeur de 0 indique "ne jamais mettre en cache". Une valeur de -1 indique "cache pour toujours".

Byron Whitlock
la source
7
Remarque: cela ne désactive pas toute la mise en cache DNS de votre système d'exploitation. Désactive simplement la mise en cache en mémoire interrompue de Java dans la bibliothèque. Vous pouvez simplement définir ces propriétés sur la ligne de commande lorsque vous appelez la JVM.
Nelson
2
Je ne sais pas que «cassé» est valide. Java (pour des raisons de sécurité) met en cache les entrées DNS pour toujours, ou jusqu'au redémarrage de la machine virtuelle Java, selon la première éventualité. Cela (d'après ce que je peux dire) était intentionnel. Les paramètres peuvent être définis dans le fichier de stratégie java.security ou sur la ligne de commande. Les paramètres sont différents pour chacun. Référence: rgagnon.com/javadetails/java-0445.html
Milner
4
Notez que vous ne pouvez pas les définir comme propriétés système (c'est-à-dire en utilisant les indicateurs -D ou System.setProperty) car ce ne sont pas des propriétés système - ce sont des propriétés de sécurité.
Les Hazlewood
6
Cette documentation est légèrement différente dans la version 1.7. Plus précisément, le cache pour toujours ne se produit désormais que lorsqu'un gestionnaire de sécurité est présent: "Le comportement par défaut est de mettre en cache pour toujours lorsqu'un gestionnaire de sécurité est installé et de mettre en cache pendant une période spécifique d'implémentation, lorsqu'un gestionnaire de sécurité n'est pas installé." docs.oracle.com/javase/7/docs/technotes/guides/net/…
Brett Okken
1
@Michael voir System.getSecurityManager(). Docs pour Java 8: docs.oracle.com/javase/8/docs/api/java/lang/…
gesellix
22

Cela a évidemment été corrigé dans les versions plus récentes (SE 6 et 7). Je rencontre un temps de mise en cache de 30 secondes maximum lors de l'exécution de l'extrait de code suivant tout en regardant l'activité du port 53 à l'aide de tcpdump.

/**
 * http://stackoverflow.com/questions/1256556/any-way-to-make-java-honor-the-dns-caching-timeout-ttl
 *
 * Result: Java 6 distributed with Ubuntu 12.04 and Java 7 u15 downloaded from Oracle have
 * an expiry time for dns lookups of approx. 30 seconds.
 */

import java.util.*;
import java.text.*;
import java.security.*;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

public class Test {
    final static String hostname = "www.google.com";
    public static void main(String[] args) {
        // only required for Java SE 5 and lower:
        //Security.setProperty("networkaddress.cache.ttl", "30");

        System.out.println(Security.getProperty("networkaddress.cache.ttl"));
        System.out.println(System.getProperty("networkaddress.cache.ttl"));
        System.out.println(Security.getProperty("networkaddress.cache.negative.ttl"));
        System.out.println(System.getProperty("networkaddress.cache.negative.ttl"));

        while(true) {
            int i = 0;
            try {
                makeRequest();
                InetAddress inetAddress = InetAddress.getLocalHost();
                System.out.println(new Date());
                inetAddress = InetAddress.getByName(hostname);
                displayStuff(hostname, inetAddress);
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
            try {
                Thread.sleep(5L*1000L);
            } catch(Exception ex) {}
            i++;
        }
    }

    public static void displayStuff(String whichHost, InetAddress inetAddress) {
        System.out.println("Which Host:" + whichHost);
        System.out.println("Canonical Host Name:" + inetAddress.getCanonicalHostName());
        System.out.println("Host Name:" + inetAddress.getHostName());
        System.out.println("Host Address:" + inetAddress.getHostAddress());
    }

    public static void makeRequest() {
        try {
            URL url = new URL("http://"+hostname+"/");
            URLConnection conn = url.openConnection();
            conn.connect();
            InputStream is = conn.getInputStream();
            InputStreamReader ird = new InputStreamReader(is);
            BufferedReader rd = new BufferedReader(ird);
            String res;
            while((res = rd.readLine()) != null) {
                System.out.println(res);
                break;
            }
            rd.close();
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
}
utilisateur1050755
la source
16
Oui, Java 1.5 avait une valeur par défaut de mise en cache infinie. Java 1.6 et 1.7 ont une valeur par défaut de 30 secondes.
Michael
7
La documentation de la version 1.7 indique que cela ne peut être vrai que dans le cas où un gestionnaire de sécurité n’est pas présent: «Le comportement par défaut est de mettre en cache pour toujours quand un gestionnaire de sécurité est installé, et de mettre en cache pendant une période spécifique d’implémentation, quand une sécurité le gestionnaire n'est pas installé. " docs.oracle.com/javase/7/docs/technotes/guides/net/…
Brett Okken
1
@Michael souhaite partager la source de cette information?
rustyx
4
@rustyx Oracle 1.6 et 1.7 JDK a ceci dans jre / lib / security / java.security pour networkaddress.cache.ttl: "# la valeur par défaut est pour toujours (FOREVER). Pour des raisons de sécurité, cette # mise en cache est faite pour toujours quand un gestionnaire de sécurité est défini. Lorsqu'un gestionnaire de # sécurité n'est pas défini, le comportement par défaut est de mettre en cache pendant 30 secondes. " Ainsi, les applets et applications déployées via Java Web Start mettent toujours en cache pour toujours, sinon c'est 30 secondes.
Michael
1
Voici un pointeur de code vers java.security d'OpenJDK 8, qui indique que sans gestionnaire de sécurité, le TTL est de 30 s: hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/f940e7a48b72/src/share/… . J'ai testé cela sur Mac OS X et Ubuntu 14.04.
tro
18

Pour développer la réponse de Byron, je pense que vous devez modifier le fichier java.securitydans le %JRE_HOME%\lib\securityrépertoire pour effectuer ce changement.

Voici la section pertinente:

#
# 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 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 

Documentation sur le java.securityfichier ici .

mat b
la source
5
Pour ajouter à cela, lors de l'utilisation de tomcat6, j'ai dû modifier mon fichier lib / security, car la configuration de networkaddress.cache.ttl ou sun.net.inetaddr.ttl soit par programme, soit via la variable JAVA_OPTS ne fonctionnait pas.
bramp le
1
@bramp Merci mon frère, je suis également confronté au même problème et résolu en utilisant votre commentaire et vos réponses +1 pour un commentaire et une réponse.
Bhavik Ambani
7

Pour résumer les autres réponses, dans <jre-path>/lib/security/java.securityvous pouvez définir la valeur de la propriété networkaddress.cache.ttlpour ajuster la façon dont les recherches DNS sont mises en cache. Notez qu'il ne s'agit pas d' une propriété système mais d'une propriété de sécurité. J'ai pu définir ceci en utilisant:

java.security.Security.setProperty("networkaddress.cache.ttl", "<value>");

Cela peut également être défini par la propriété système -Dsun.net.inetaddr.ttl que cela ne remplace pas une propriété de sécurité si elle est définie ailleurs.

Je voudrais également ajouter que si vous rencontrez ce problème avec les services Web dans WebSphere, comme je l'étais, la configuration networkaddress.cache.ttlne sera pas suffisante. Vous devez définir la propriété système disableWSAddressCachingsur true. Contrairement à la propriété time-to-live, cela peut être défini comme un argument JVM ou via System.setProperty).

IBM a publié un article assez détaillé sur la manière dont WebSphere gère la mise en cache DNS ici . La pièce pertinente à ce qui précède est:

Pour désactiver la mise en cache d'adresses pour les services Web, vous devez définir une propriété personnalisée JVM supplémentaire disableWSAddressCaching sur true. Utilisez cette propriété pour désactiver la mise en cache d'adresses pour les services Web. Si votre système s'exécute généralement avec de nombreux threads clients et que vous rencontrez un conflit de verrouillage sur le cache wsAddrCache, vous pouvez définir cette propriété personnalisée sur true pour empêcher la mise en cache des données des services Web.

la racine carrée
la source
2

Selon les propriétés officielles d'Oracle Java , il sun.net.inetaddr.ttls'agit d'une propriété spécifique à l'implémentation de Sun, qui "peut ne pas être prise en charge dans les versions futures". "la meilleure façon est d'utiliser la propriété de sécurité" networkaddress.cache.ttl.

CloudStax
la source