mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
8304885: Reuse stale data to improve DNS resolver resiliency
Reviewed-by: michaelm, dfuchs
This commit is contained in:
parent
beec734fdd
commit
bdd81b3182
16 changed files with 461 additions and 87 deletions
|
@ -52,6 +52,7 @@ import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.ConcurrentSkipListSet;
|
import java.util.concurrent.ConcurrentSkipListSet;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@ -191,9 +192,9 @@ import static java.net.spi.InetAddressResolver.LookupPolicy.IPV6_FIRST;
|
||||||
* <p> If the default behavior is not desired, then a Java security property
|
* <p> 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
|
* can be set to a different Time-to-live (TTL) value for positive
|
||||||
* caching. Likewise, a system admin can configure a different
|
* caching. Likewise, a system admin can configure a different
|
||||||
* negative caching TTL value when needed.
|
* negative caching TTL value when needed or extend the usage of the stale data.
|
||||||
*
|
*
|
||||||
* <p> Two Java security properties control the TTL values used for
|
* <p> Three Java security properties control the TTL values used for
|
||||||
* positive and negative host name resolution caching:
|
* positive and negative host name resolution caching:
|
||||||
*
|
*
|
||||||
* <dl style="margin-left:2em">
|
* <dl style="margin-left:2em">
|
||||||
|
@ -205,6 +206,25 @@ import static java.net.spi.InetAddressResolver.LookupPolicy.IPV6_FIRST;
|
||||||
* <p>
|
* <p>
|
||||||
* A value of -1 indicates "cache forever".
|
* A value of -1 indicates "cache forever".
|
||||||
* </dd>
|
* </dd>
|
||||||
|
* <dt><b>networkaddress.cache.stale.ttl</b></dt>
|
||||||
|
* <dd>Indicates the caching policy for stale names. The value is specified as
|
||||||
|
* an integer to indicate the number of seconds that stale names will be kept in
|
||||||
|
* the cache. A name is considered stale if the TTL has expired and an attempt
|
||||||
|
* to lookup the host name again was not successful. This property is useful if
|
||||||
|
* it is preferable to use a stale name rather than fail due to an unsuccessful
|
||||||
|
* lookup. The default setting is to cache for an implementation specific period
|
||||||
|
* of time.
|
||||||
|
* <p>
|
||||||
|
* If the value of this property is larger than "networkaddress.cache.ttl" then
|
||||||
|
* "networkaddress.cache.ttl" will be used as a refresh interval of the name in
|
||||||
|
* the cache. For example, if this property is set to 1 day and
|
||||||
|
* "networkaddress.cache.ttl" is set to 30 seconds, then the positive response
|
||||||
|
* will be cached for 1 day but an attempt to refresh it will be done every
|
||||||
|
* 30 seconds.
|
||||||
|
* <p>
|
||||||
|
* A value of 0 (zero) or if the property is not set means do not use stale
|
||||||
|
* names. Negative values are ignored.
|
||||||
|
* </dd>
|
||||||
* <dt><b>networkaddress.cache.negative.ttl</b> (default: 10)</dt>
|
* <dt><b>networkaddress.cache.negative.ttl</b> (default: 10)</dt>
|
||||||
* <dd>Indicates the caching policy for un-successful name lookups
|
* <dd>Indicates the caching policy for un-successful name lookups
|
||||||
* from the name service. The value is specified as an integer to
|
* from the name service. The value is specified as an integer to
|
||||||
|
@ -933,7 +953,7 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
||||||
|
|
||||||
// CachedAddresses that have to expire are kept ordered in this NavigableSet
|
// CachedAddresses that have to expire are kept ordered in this NavigableSet
|
||||||
// which is scanned on each access
|
// which is scanned on each access
|
||||||
private static final NavigableSet<CachedAddresses> expirySet =
|
private static final NavigableSet<CachedLookup> expirySet =
|
||||||
new ConcurrentSkipListSet<>();
|
new ConcurrentSkipListSet<>();
|
||||||
|
|
||||||
// common interface
|
// common interface
|
||||||
|
@ -941,15 +961,22 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
||||||
InetAddress[] get() throws UnknownHostException;
|
InetAddress[] get() throws UnknownHostException;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a holder for cached addresses with required metadata
|
/**
|
||||||
private static final class CachedAddresses implements Addresses, Comparable<CachedAddresses> {
|
* A cached result of a name service lookup. The result can be either valid
|
||||||
|
* addresses or invalid (ie a failed lookup) containing no addresses.
|
||||||
|
*/
|
||||||
|
private static class CachedLookup implements Addresses, Comparable<CachedLookup> {
|
||||||
private static final AtomicLong seq = new AtomicLong();
|
private static final AtomicLong seq = new AtomicLong();
|
||||||
final String host;
|
final String host;
|
||||||
final InetAddress[] inetAddresses;
|
volatile InetAddress[] inetAddresses;
|
||||||
final long expiryTime; // time of expiry (in terms of System.nanoTime())
|
/**
|
||||||
|
* Time of expiry (in terms of System.nanoTime()). Can be modified only
|
||||||
|
* when the record is not added to the "expirySet".
|
||||||
|
*/
|
||||||
|
volatile long expiryTime;
|
||||||
final long id = seq.incrementAndGet(); // each instance is unique
|
final long id = seq.incrementAndGet(); // each instance is unique
|
||||||
|
|
||||||
CachedAddresses(String host, InetAddress[] inetAddresses, long expiryTime) {
|
CachedLookup(String host, InetAddress[] inetAddresses, long expiryTime) {
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.inetAddresses = inetAddresses;
|
this.inetAddresses = inetAddresses;
|
||||||
this.expiryTime = expiryTime;
|
this.expiryTime = expiryTime;
|
||||||
|
@ -964,7 +991,7 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(CachedAddresses other) {
|
public int compareTo(CachedLookup other) {
|
||||||
// natural order is expiry time -
|
// natural order is expiry time -
|
||||||
// compare difference of expiry times rather than
|
// compare difference of expiry times rather than
|
||||||
// expiry times directly, to avoid possible overflow.
|
// expiry times directly, to avoid possible overflow.
|
||||||
|
@ -975,6 +1002,106 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
||||||
// ties are broken using unique id
|
// ties are broken using unique id
|
||||||
return Long.compare(this.id, other.id);
|
return Long.compare(this.id, other.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current cache record is expired or not. Expired records
|
||||||
|
* are removed from the expirySet and cache.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the record was removed
|
||||||
|
*/
|
||||||
|
public boolean tryRemoveExpiredAddress(long now) {
|
||||||
|
// compare difference of time instants rather than
|
||||||
|
// time instants directly, to avoid possible overflow.
|
||||||
|
// (see System.nanoTime() recommendations...)
|
||||||
|
if ((expiryTime - now) < 0L) {
|
||||||
|
// ConcurrentSkipListSet uses weakly consistent iterator,
|
||||||
|
// so removing while iterating is OK...
|
||||||
|
if (expirySet.remove(this)) {
|
||||||
|
// ... remove from cache
|
||||||
|
cache.remove(host, this);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cached valid lookup containing addresses whose validity may be
|
||||||
|
* temporarily extended by an additional stale period pending the mapping
|
||||||
|
* being refreshed or updated.
|
||||||
|
*/
|
||||||
|
private static final class ValidCachedLookup extends CachedLookup {
|
||||||
|
/**
|
||||||
|
* Time to refresh (in terms of System.nanoTime()).
|
||||||
|
*/
|
||||||
|
private volatile long refreshTime;
|
||||||
|
/**
|
||||||
|
* For how long the stale data should be used after TTL expiration.
|
||||||
|
* Initially equal to the expiryTime, but increased over time after each
|
||||||
|
* successful lookup.
|
||||||
|
*/
|
||||||
|
private volatile long staleTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* only one thread is doing lookup to name service
|
||||||
|
* for particular host at any time.
|
||||||
|
*/
|
||||||
|
private final Lock lookupLock = new ReentrantLock();
|
||||||
|
|
||||||
|
ValidCachedLookup(String host, InetAddress[] inetAddresses,
|
||||||
|
long staleTime, long refreshTime)
|
||||||
|
{
|
||||||
|
super(host, inetAddresses, staleTime);
|
||||||
|
this.refreshTime = refreshTime;
|
||||||
|
this.staleTime = staleTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetAddress[] get() {
|
||||||
|
long now = System.nanoTime();
|
||||||
|
if ((refreshTime - now) < 0L && lookupLock.tryLock()) {
|
||||||
|
try {
|
||||||
|
// cachePolicy is in [s] - we need [ns]
|
||||||
|
refreshTime = now + InetAddressCachePolicy.get() * 1000_000_000L;
|
||||||
|
// getAddressesFromNameService returns non-empty/non-null value
|
||||||
|
inetAddresses = getAddressesFromNameService(host);
|
||||||
|
// don't update the "expirySet", will do that later
|
||||||
|
staleTime = refreshTime + InetAddressCachePolicy.getStale() * 1000_000_000L;
|
||||||
|
} catch (UnknownHostException ignore) {
|
||||||
|
} finally {
|
||||||
|
lookupLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inetAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides the parent method to skip deleting the record from the
|
||||||
|
* cache if the stale data can still be used. Note to update the
|
||||||
|
* "expiryTime" field we have to remove the record from the expirySet
|
||||||
|
* and add it back. It is not necessary to remove/add it here, we can do
|
||||||
|
* that in the "get()" method above, but extracting it minimizes
|
||||||
|
* contention on "expirySet".
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean tryRemoveExpiredAddress(long now) {
|
||||||
|
// compare difference of time instants rather than
|
||||||
|
// time instants directly, to avoid possible overflow.
|
||||||
|
// (see System.nanoTime() recommendations...)
|
||||||
|
if ((expiryTime - now) < 0L) {
|
||||||
|
if ((staleTime - now) < 0L) {
|
||||||
|
return super.tryRemoveExpiredAddress(now);
|
||||||
|
}
|
||||||
|
// ConcurrentSkipListSet uses weakly consistent iterator,
|
||||||
|
// so removing while iterating is OK...
|
||||||
|
if (expirySet.remove(this)) {
|
||||||
|
expiryTime = staleTime;
|
||||||
|
expirySet.add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// a name service lookup based Addresses implementation which replaces itself
|
// a name service lookup based Addresses implementation which replaces itself
|
||||||
|
@ -1021,18 +1148,33 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
||||||
if (cachePolicy == InetAddressCachePolicy.NEVER) {
|
if (cachePolicy == InetAddressCachePolicy.NEVER) {
|
||||||
cache.remove(host, this);
|
cache.remove(host, this);
|
||||||
} else {
|
} else {
|
||||||
CachedAddresses cachedAddresses = new CachedAddresses(
|
long now = System.nanoTime();
|
||||||
host,
|
long expiryTime =
|
||||||
inetAddresses,
|
cachePolicy == InetAddressCachePolicy.FOREVER ?
|
||||||
cachePolicy == InetAddressCachePolicy.FOREVER
|
0L
|
||||||
? 0L
|
// cachePolicy is in [s] - we need [ns]
|
||||||
// cachePolicy is in [s] - we need [ns]
|
: now + 1000_000_000L * cachePolicy;
|
||||||
: System.nanoTime() + 1000_000_000L * cachePolicy
|
CachedLookup cachedLookup;
|
||||||
);
|
if (InetAddressCachePolicy.getStale() > 0 &&
|
||||||
if (cache.replace(host, this, cachedAddresses) &&
|
ex == null && expiryTime > 0)
|
||||||
|
{
|
||||||
|
long refreshTime = expiryTime;
|
||||||
|
// staleCachePolicy is in [s] - we need [ns]
|
||||||
|
expiryTime = refreshTime + 1000_000_000L *
|
||||||
|
InetAddressCachePolicy.getStale();
|
||||||
|
cachedLookup = new ValidCachedLookup(host,
|
||||||
|
inetAddresses,
|
||||||
|
expiryTime,
|
||||||
|
refreshTime);
|
||||||
|
} else {
|
||||||
|
cachedLookup = new CachedLookup(host,
|
||||||
|
inetAddresses,
|
||||||
|
expiryTime);
|
||||||
|
}
|
||||||
|
if (cache.replace(host, this, cachedLookup) &&
|
||||||
cachePolicy != InetAddressCachePolicy.FOREVER) {
|
cachePolicy != InetAddressCachePolicy.FOREVER) {
|
||||||
// schedule expiry
|
// schedule expiry
|
||||||
expirySet.add(cachedAddresses);
|
expirySet.add(cachedLookup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (inetAddresses == null || inetAddresses.length == 0) {
|
if (inetAddresses == null || inetAddresses.length == 0) {
|
||||||
|
@ -1638,18 +1780,8 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
||||||
// remove expired addresses from cache - expirySet keeps them ordered
|
// remove expired addresses from cache - expirySet keeps them ordered
|
||||||
// by expiry time so we only need to iterate the prefix of the NavigableSet...
|
// by expiry time so we only need to iterate the prefix of the NavigableSet...
|
||||||
long now = System.nanoTime();
|
long now = System.nanoTime();
|
||||||
for (CachedAddresses caddrs : expirySet) {
|
for (CachedLookup caddrs : expirySet) {
|
||||||
// compare difference of time instants rather than
|
if (!caddrs.tryRemoveExpiredAddress(now)) {
|
||||||
// time instants directly, to avoid possible overflow.
|
|
||||||
// (see System.nanoTime() recommendations...)
|
|
||||||
if ((caddrs.expiryTime - now) < 0L) {
|
|
||||||
// ConcurrentSkipListSet uses weakly consistent iterator,
|
|
||||||
// so removing while iterating is OK...
|
|
||||||
if (expirySet.remove(caddrs)) {
|
|
||||||
// ... remove from cache
|
|
||||||
cache.remove(caddrs.host, caddrs);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// we encountered 1st element that expires in future
|
// we encountered 1st element that expires in future
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1662,7 +1794,7 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
||||||
} else {
|
} else {
|
||||||
addrs = cache.remove(host);
|
addrs = cache.remove(host);
|
||||||
if (addrs != null) {
|
if (addrs != null) {
|
||||||
if (addrs instanceof CachedAddresses) {
|
if (addrs instanceof CachedLookup) {
|
||||||
// try removing from expirySet too if CachedAddresses
|
// try removing from expirySet too if CachedAddresses
|
||||||
expirySet.remove(addrs);
|
expirySet.remove(addrs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<!DOCTYPE HTML>
|
<!DOCTYPE HTML>
|
||||||
<!--
|
<!--
|
||||||
Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
|
Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
|
||||||
This code is free software; you can redistribute it and/or modify it
|
This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -267,13 +267,22 @@ tuning on how the cache is operating.</P>
|
||||||
policy, while a value of 0 (zero) means no caching. The default value
|
policy, while a value of 0 (zero) means no caching. The default value
|
||||||
is -1 (forever) if a security manager is installed, and implementation-specific
|
is -1 (forever) if a security manager is installed, and implementation-specific
|
||||||
when no security manager is installed.</P>
|
when no security manager is installed.</P>
|
||||||
|
<LI><P><B>{@systemProperty networkaddress.cache.stale.ttl}</B> (default: see below)<BR>
|
||||||
|
Value is an integer corresponding to the number of seconds that stale names
|
||||||
|
will be kept in the cache. A name is considered stale if the TTL has expired
|
||||||
|
and an attempt to lookup the host name again was not successful. This
|
||||||
|
property is useful if it is preferable to use a stale name rather than
|
||||||
|
fail due to an unsuccessful lookup.
|
||||||
|
A value of 0 (zero) or if the property is not set means do not use stale
|
||||||
|
names. Negative values are ignored.
|
||||||
|
The default value is implementation-specific.</P>
|
||||||
<LI><P><B>{@systemProperty networkaddress.cache.negative.ttl}</B> (default: {@code 10})<BR>
|
<LI><P><B>{@systemProperty networkaddress.cache.negative.ttl}</B> (default: {@code 10})<BR>
|
||||||
Value is an integer corresponding to the number of seconds an
|
Value is an integer corresponding to the number of seconds an
|
||||||
unsuccessful name lookup will be kept in the cache. A value of -1,
|
unsuccessful name lookup will be kept in the cache. A value of -1,
|
||||||
or any negative value, means “cache forever”, while a
|
or any negative value, means “cache forever”, while a
|
||||||
value of 0 (zero) means no caching.</P>
|
value of 0 (zero) means no caching.</P>
|
||||||
</UL>
|
</UL>
|
||||||
<P>Since these 2 properties are part of the security policy, they are
|
<P>Since these 3 properties are part of the security policy, they are
|
||||||
not set by either the -D option or the {@code System.setProperty()} API,
|
not set by either the -D option or the {@code System.setProperty()} API,
|
||||||
instead they are set as security properties.</P>
|
instead they are set as security properties.</P>
|
||||||
<a id="Unixdomain"></a>
|
<a id="Unixdomain"></a>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -27,6 +27,7 @@ package sun.net;
|
||||||
|
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@SuppressWarnings("removal")
|
@SuppressWarnings("removal")
|
||||||
public final class InetAddressCachePolicy {
|
public final class InetAddressCachePolicy {
|
||||||
|
@ -36,6 +37,12 @@ public final class InetAddressCachePolicy {
|
||||||
private static final String cachePolicyPropFallback =
|
private static final String cachePolicyPropFallback =
|
||||||
"sun.net.inetaddr.ttl";
|
"sun.net.inetaddr.ttl";
|
||||||
|
|
||||||
|
// Controls the cache stale policy for successful lookups only
|
||||||
|
private static final String cacheStalePolicyProp =
|
||||||
|
"networkaddress.cache.stale.ttl";
|
||||||
|
private static final String cacheStalePolicyPropFallback =
|
||||||
|
"sun.net.inetaddr.stale.ttl";
|
||||||
|
|
||||||
// Controls the cache policy for negative lookups only
|
// Controls the cache policy for negative lookups only
|
||||||
private static final String negativeCachePolicyProp =
|
private static final String negativeCachePolicyProp =
|
||||||
"networkaddress.cache.negative.ttl";
|
"networkaddress.cache.negative.ttl";
|
||||||
|
@ -59,6 +66,15 @@ public final class InetAddressCachePolicy {
|
||||||
*/
|
*/
|
||||||
private static volatile int cachePolicy = FOREVER;
|
private static volatile int cachePolicy = FOREVER;
|
||||||
|
|
||||||
|
/* The Java-level namelookup cache stale policy:
|
||||||
|
*
|
||||||
|
* any positive value: the number of seconds to use the stale names
|
||||||
|
* zero: do not use stale names
|
||||||
|
*
|
||||||
|
* default value is never (NEVER).
|
||||||
|
*/
|
||||||
|
private static volatile int staleCachePolicy = NEVER;
|
||||||
|
|
||||||
/* The Java-level namelookup cache policy for negative lookups:
|
/* The Java-level namelookup cache policy for negative lookups:
|
||||||
*
|
*
|
||||||
* -1: caching forever
|
* -1: caching forever
|
||||||
|
@ -85,31 +101,7 @@ public final class InetAddressCachePolicy {
|
||||||
* Initialize
|
* Initialize
|
||||||
*/
|
*/
|
||||||
static {
|
static {
|
||||||
|
Integer tmp = getProperty(cachePolicyProp, cachePolicyPropFallback);
|
||||||
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) {
|
if (tmp != null) {
|
||||||
cachePolicy = tmp < 0 ? FOREVER : tmp;
|
cachePolicy = tmp < 0 ? FOREVER : tmp;
|
||||||
propertySet = true;
|
propertySet = true;
|
||||||
|
@ -121,40 +113,60 @@ public final class InetAddressCachePolicy {
|
||||||
cachePolicy = DEFAULT_POSITIVE;
|
cachePolicy = DEFAULT_POSITIVE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tmp = java.security.AccessController.doPrivileged (
|
tmp = getProperty(negativeCachePolicyProp,
|
||||||
new PrivilegedAction<Integer>() {
|
negativeCachePolicyPropFallback);
|
||||||
public Integer run() {
|
|
||||||
try {
|
|
||||||
String tmpString = Security.getProperty(negativeCachePolicyProp);
|
|
||||||
if (tmpString != null) {
|
|
||||||
return Integer.valueOf(tmpString);
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException ignored) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
String tmpString = System.getProperty(negativeCachePolicyPropFallback);
|
|
||||||
if (tmpString != null) {
|
|
||||||
return Integer.decode(tmpString);
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException ignored) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (tmp != null) {
|
if (tmp != null) {
|
||||||
negativeCachePolicy = tmp < 0 ? FOREVER : tmp;
|
negativeCachePolicy = tmp < 0 ? FOREVER : tmp;
|
||||||
propertyNegativeSet = true;
|
propertyNegativeSet = true;
|
||||||
}
|
}
|
||||||
|
if (cachePolicy > 0) {
|
||||||
|
tmp = getProperty(cacheStalePolicyProp,
|
||||||
|
cacheStalePolicyPropFallback);
|
||||||
|
if (tmp != null) {
|
||||||
|
staleCachePolicy = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Integer getProperty(String cachePolicyProp,
|
||||||
|
String cachePolicyPropFallback)
|
||||||
|
{
|
||||||
|
return 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int get() {
|
public static int get() {
|
||||||
return cachePolicy;
|
return cachePolicy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getStale() {
|
||||||
|
return staleCachePolicy;
|
||||||
|
}
|
||||||
|
|
||||||
public static int getNegative() {
|
public static int getNegative() {
|
||||||
return negativeCachePolicy;
|
return negativeCachePolicy;
|
||||||
}
|
}
|
||||||
|
|
|
@ -357,6 +357,17 @@ ssl.TrustManagerFactory.algorithm=PKIX
|
||||||
#
|
#
|
||||||
#networkaddress.cache.ttl=-1
|
#networkaddress.cache.ttl=-1
|
||||||
|
|
||||||
|
#
|
||||||
|
# The Java-level namelookup cache stale policy:
|
||||||
|
#
|
||||||
|
# any positive value: the number of seconds to use the stale names
|
||||||
|
# zero: do not use stale names
|
||||||
|
# negative values are ignored
|
||||||
|
#
|
||||||
|
# default value is 0 (NEVER).
|
||||||
|
#
|
||||||
|
#networkaddress.cache.stale.ttl=0
|
||||||
|
|
||||||
# The Java-level namelookup cache policy for failed lookups:
|
# The Java-level namelookup cache policy for failed lookups:
|
||||||
#
|
#
|
||||||
# any negative value: cache forever
|
# any negative value: cache forever
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -37,9 +37,27 @@ import impl.SimpleResolverProviderImpl;
|
||||||
* @library lib providers/simple
|
* @library lib providers/simple
|
||||||
* @build test.library/testlib.ResolutionRegistry
|
* @build test.library/testlib.ResolutionRegistry
|
||||||
* simple.provider/impl.SimpleResolverProviderImpl AddressesCachingTest
|
* simple.provider/impl.SimpleResolverProviderImpl AddressesCachingTest
|
||||||
* @run testng/othervm -Djava.security.properties=${test.src}/NeverCache.props
|
* @run testng/othervm -Djava.security.properties=${test.src}/props/NeverCache.props
|
||||||
* -Dtest.cachingDisabled=true AddressesCachingTest
|
* -Dtest.cachingDisabled=true AddressesCachingTest
|
||||||
* @run testng/othervm -Djava.security.properties=${test.src}/ForeverCache.props
|
* @run testng/othervm -Djava.security.properties=${test.src}/props/ForeverCache.props
|
||||||
|
* -Dtest.cachingDisabled=false AddressesCachingTest
|
||||||
|
* @run testng/othervm
|
||||||
|
* -Djava.security.properties=${test.src}/props/NeverCacheIgnoreMinusStale.props
|
||||||
|
* -Dtest.cachingDisabled=true AddressesCachingTest
|
||||||
|
* @run testng/othervm
|
||||||
|
* -Djava.security.properties=${test.src}/props/NeverCacheIgnorePositiveStale.props
|
||||||
|
* -Dtest.cachingDisabled=true AddressesCachingTest
|
||||||
|
* @run testng/othervm
|
||||||
|
* -Djava.security.properties=${test.src}/props/NeverCacheIgnoreZeroStale.props
|
||||||
|
* -Dtest.cachingDisabled=true AddressesCachingTest
|
||||||
|
* @run testng/othervm
|
||||||
|
* -Djava.security.properties=${test.src}/props/ForeverCacheIgnoreMinusStale.props
|
||||||
|
* -Dtest.cachingDisabled=false AddressesCachingTest
|
||||||
|
* @run testng/othervm
|
||||||
|
* -Djava.security.properties=${test.src}/props/ForeverCacheIgnorePositiveStale.props
|
||||||
|
* -Dtest.cachingDisabled=false AddressesCachingTest
|
||||||
|
* @run testng/othervm
|
||||||
|
* -Djava.security.properties=${test.src}/props/ForeverCacheIgnoreZeroStale.props
|
||||||
* -Dtest.cachingDisabled=false AddressesCachingTest
|
* -Dtest.cachingDisabled=false AddressesCachingTest
|
||||||
*/
|
*/
|
||||||
public class AddressesCachingTest {
|
public class AddressesCachingTest {
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import impl.SimpleResolverProviderImpl;
|
||||||
|
import org.testng.Assert;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @summary Test that stale InetAddress caching security properties work as
|
||||||
|
* expected when a custom resolver is installed.
|
||||||
|
* @library lib providers/simple
|
||||||
|
* @build test.library/testlib.ResolutionRegistry
|
||||||
|
* simple.provider/impl.SimpleResolverProviderImpl AddressesStaleCachingTest
|
||||||
|
* @run testng/othervm -Djava.security.properties=${test.src}/props/CacheStale.props AddressesStaleCachingTest
|
||||||
|
*/
|
||||||
|
public class AddressesStaleCachingTest {
|
||||||
|
|
||||||
|
private static class Lookup {
|
||||||
|
private final byte[] address;
|
||||||
|
private final long timestamp;
|
||||||
|
|
||||||
|
private Lookup(byte[] address, long timestamp) {
|
||||||
|
this.address = address;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates successful and unsuccessful lookups when the stale cache is
|
||||||
|
* enabled.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRefresh() throws Exception{
|
||||||
|
// The first request is to save the data into the cache
|
||||||
|
Lookup first = doLookup(false, 0);
|
||||||
|
|
||||||
|
Thread.sleep(10000); // intentionally big delay > x2 stale property
|
||||||
|
// The refreshTime is expired, we will do the successful lookup.
|
||||||
|
Lookup second = doLookup(false, 0);
|
||||||
|
Assert.assertNotEquals(first.timestamp, second.timestamp,
|
||||||
|
"Two lookups are expected");
|
||||||
|
|
||||||
|
Thread.sleep(10000); // intentionally big delay > x2 stale property
|
||||||
|
// The refreshTime is expired again, we will do the failed lookup.
|
||||||
|
Lookup third = doLookup(true, 0);
|
||||||
|
Assert.assertNotEquals(second.timestamp, third.timestamp,
|
||||||
|
"Two lookups are expected");
|
||||||
|
|
||||||
|
// The stale cache is enabled, so we should get valid/same data for
|
||||||
|
// all requests(even for the failed request).
|
||||||
|
Assert.assertEquals(first.address, second.address,
|
||||||
|
"Same address is expected");
|
||||||
|
Assert.assertEquals(second.address, third.address,
|
||||||
|
"Same address is expected");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that only one thread is blocked during "refresh", all others
|
||||||
|
* will continue to use the "stale" data.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testOnlyOneThreadIsBlockedDuringRefresh() throws Exception {
|
||||||
|
long timeout = System.nanoTime() + TimeUnit.SECONDS.toNanos(12);
|
||||||
|
doLookup(false, timeout);
|
||||||
|
Thread.sleep(9000);
|
||||||
|
|
||||||
|
CountDownLatch blockServer = new CountDownLatch(1);
|
||||||
|
SimpleResolverProviderImpl.setBlocker(blockServer);
|
||||||
|
|
||||||
|
Thread ts[] = new Thread[10];
|
||||||
|
CountDownLatch wait9 = new CountDownLatch(ts.length - 1);
|
||||||
|
CountDownLatch wait10 = new CountDownLatch(ts.length);
|
||||||
|
CountDownLatch start = new CountDownLatch(ts.length);
|
||||||
|
for (int i = 0; i < ts.length; i++) {
|
||||||
|
ts[i] = new Thread(() -> {
|
||||||
|
start.countDown();
|
||||||
|
try {
|
||||||
|
start.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
doLookup(true, timeout);
|
||||||
|
wait9.countDown();
|
||||||
|
wait10.countDown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (Thread t : ts) {
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
if (!wait9.await(10, TimeUnit.SECONDS)) {
|
||||||
|
blockServer.countDown();
|
||||||
|
throw new RuntimeException("Some threads hang");
|
||||||
|
}
|
||||||
|
blockServer.countDown();
|
||||||
|
if (!wait10.await(10, TimeUnit.SECONDS)) {
|
||||||
|
throw new RuntimeException("The last thread hangs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Lookup doLookup(boolean error, long timeout) {
|
||||||
|
SimpleResolverProviderImpl.setUnreachableServer(error);
|
||||||
|
try {
|
||||||
|
byte[] firstAddress = InetAddress.getByName("javaTest.org").getAddress();
|
||||||
|
long firstTimestamp = SimpleResolverProviderImpl.getLastLookupTimestamp();
|
||||||
|
|
||||||
|
byte[] secondAddress = InetAddress.getByName("javaTest.org").getAddress();
|
||||||
|
long secondTimestamp = SimpleResolverProviderImpl.getLastLookupTimestamp();
|
||||||
|
|
||||||
|
Assert.assertEquals(firstAddress, secondAddress,
|
||||||
|
"Same address is expected");
|
||||||
|
if (timeout == 0 || timeout - System.nanoTime() > 0) {
|
||||||
|
Assert.assertEquals(firstTimestamp, secondTimestamp,
|
||||||
|
"Only one positive lookup is expected with caching enabled");
|
||||||
|
}
|
||||||
|
return new Lookup(firstAddress, firstTimestamp);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
networkaddress.cache.ttl=7
|
||||||
|
networkaddress.cache.negative.ttl=3
|
||||||
|
networkaddress.cache.stale.ttl=30
|
|
@ -0,0 +1,3 @@
|
||||||
|
networkaddress.cache.ttl=-1
|
||||||
|
networkaddress.cache.negative.ttl=-1
|
||||||
|
networkaddress.cache.stale.ttl=-1
|
|
@ -0,0 +1,3 @@
|
||||||
|
networkaddress.cache.ttl=-1
|
||||||
|
networkaddress.cache.negative.ttl=-1
|
||||||
|
networkaddress.cache.stale.ttl=10000
|
|
@ -0,0 +1,3 @@
|
||||||
|
networkaddress.cache.ttl=-1
|
||||||
|
networkaddress.cache.negative.ttl=-1
|
||||||
|
networkaddress.cache.stale.ttl=0
|
|
@ -0,0 +1,3 @@
|
||||||
|
networkaddress.cache.ttl=0
|
||||||
|
networkaddress.cache.negative.ttl=0
|
||||||
|
networkaddress.cache.stale.ttl=-1
|
|
@ -0,0 +1,3 @@
|
||||||
|
networkaddress.cache.ttl=0
|
||||||
|
networkaddress.cache.negative.ttl=0
|
||||||
|
networkaddress.cache.stale.ttl=10000
|
|
@ -0,0 +1,3 @@
|
||||||
|
networkaddress.cache.ttl=0
|
||||||
|
networkaddress.cache.negative.ttl=0
|
||||||
|
networkaddress.cache.stale.ttl=0
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -31,6 +31,7 @@ import java.net.spi.InetAddressResolverProvider;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@ -41,6 +42,8 @@ public class SimpleResolverProviderImpl extends InetAddressResolverProvider {
|
||||||
public static ResolutionRegistry registry = new ResolutionRegistry();
|
public static ResolutionRegistry registry = new ResolutionRegistry();
|
||||||
private static List<LookupPolicy> LOOKUP_HISTORY = Collections.synchronizedList(new ArrayList<>());
|
private static List<LookupPolicy> LOOKUP_HISTORY = Collections.synchronizedList(new ArrayList<>());
|
||||||
private static volatile long LAST_LOOKUP_TIMESTAMP;
|
private static volatile long LAST_LOOKUP_TIMESTAMP;
|
||||||
|
private static volatile boolean unreachableServer;
|
||||||
|
private static volatile CountDownLatch blocker;
|
||||||
private static Logger LOGGER = Logger.getLogger(SimpleResolverProviderImpl.class.getName());
|
private static Logger LOGGER = Logger.getLogger(SimpleResolverProviderImpl.class.getName());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -51,14 +54,27 @@ public class SimpleResolverProviderImpl extends InetAddressResolverProvider {
|
||||||
public Stream<InetAddress> lookupByName(String host, LookupPolicy lookupPolicy) throws UnknownHostException {
|
public Stream<InetAddress> lookupByName(String host, LookupPolicy lookupPolicy) throws UnknownHostException {
|
||||||
LOGGER.info("Looking-up addresses for '" + host + "'. Lookup characteristics:" +
|
LOGGER.info("Looking-up addresses for '" + host + "'. Lookup characteristics:" +
|
||||||
Integer.toString(lookupPolicy.characteristics(), 2));
|
Integer.toString(lookupPolicy.characteristics(), 2));
|
||||||
|
if (blocker != null) {
|
||||||
|
try {
|
||||||
|
blocker.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
LOOKUP_HISTORY.add(lookupPolicy);
|
LOOKUP_HISTORY.add(lookupPolicy);
|
||||||
LAST_LOOKUP_TIMESTAMP = System.nanoTime();
|
LAST_LOOKUP_TIMESTAMP = System.nanoTime();
|
||||||
|
if (unreachableServer) {
|
||||||
|
throw new UnknownHostException("unreachableServer");
|
||||||
|
}
|
||||||
return registry.lookupHost(host, lookupPolicy);
|
return registry.lookupHost(host, lookupPolicy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String lookupByAddress(byte[] addr) throws UnknownHostException {
|
public String lookupByAddress(byte[] addr) throws UnknownHostException {
|
||||||
LOGGER.info("Looking host name for the following address:" + ResolutionRegistry.addressBytesToString(addr));
|
LOGGER.info("Looking host name for the following address:" + ResolutionRegistry.addressBytesToString(addr));
|
||||||
|
if (unreachableServer) {
|
||||||
|
throw new UnknownHostException("unreachableServer");
|
||||||
|
}
|
||||||
return registry.lookupAddress(addr);
|
return registry.lookupAddress(addr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -73,6 +89,14 @@ public class SimpleResolverProviderImpl extends InetAddressResolverProvider {
|
||||||
return LAST_LOOKUP_TIMESTAMP;
|
return LAST_LOOKUP_TIMESTAMP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setUnreachableServer(boolean unreachableServer) {
|
||||||
|
SimpleResolverProviderImpl.unreachableServer = unreachableServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setBlocker(CountDownLatch blocker) {
|
||||||
|
SimpleResolverProviderImpl.blocker = blocker;
|
||||||
|
}
|
||||||
|
|
||||||
public static LookupPolicy lookupPolicyHistory(int position) {
|
public static LookupPolicy lookupPolicyHistory(int position) {
|
||||||
if (LOOKUP_HISTORY.isEmpty()) {
|
if (LOOKUP_HISTORY.isEmpty()) {
|
||||||
throw new RuntimeException("No registered lookup policies");
|
throw new RuntimeException("No registered lookup policies");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue