How to Dynamically Override Java DNS Resolution for Automated Tests
This article explains how to replace Java's default DNS resolver with a custom NameService, disable DNS caching, and manipulate DNS entries at runtime using reflection, enabling flexible, environment‑agnostic automated test cases without hard‑coding IP addresses.
Preface
Hello everyone! Starting today I will explore automated testing techniques in a series called "Playing with Automated Testing". The series will continuously update, covering pain points and solutions from VIP.com’s automation practice, as well as the internally developed framework and platform such as AutoV, Vmock-Server, and Vmock-Agent, with detailed principles and implementation.
The main goal is to deepen understanding of the architecture, principles, and implementation of the frameworks, tools, and platforms used in daily work, thereby improving automation authoring efficiency and problem‑locating capability.
Note: Most of the automation practice and related frameworks, tools, and platforms at VIP.com are developed in Java, so all code examples are Java‑based.
Abstract
First, a quick warm‑up: we will discuss how to play with DNS resolution inside automated test cases using simple "black‑tech" tricks.
When writing automated test cases you often need to call interfaces, databases, message queues, external caches, or remote servers. Hard‑coding IP addresses is fragile because IPs change, especially in containerized environments, and tests may run across multiple environments (functional, integration, staging). Using domain names instead of fixed IPs reduces maintenance cost and eases troubleshooting.
Problem
Typical approaches for domain‑based access are:
Modifying the system hosts file, which fails when multiple test instances run on the same Jenkins slave due to conflicts.
Setting an HTTP proxy (e.g., via HttpClient RequestConfig), which is framework‑specific and does not work for non‑HTTP protocols.
For client‑side applications with automatic reconnection, simulating network disconnection via iptables is problematic because iptables is Linux‑only and DNS results are cached, so changes may not take effect immediately.
Fundamentals
1. java.net.InetAddress
InetAddress is a high‑level Java class representing IP addresses. It is used by most Java networking classes such as ServerSocket, Socket, URL, DatagramSocket, etc. It provides static factory methods and several useful methods (e.g., getByName, getAllByName).
2. NameService Interface
InetAddress[] lookupAllHostAddr(String host) // get all IPs for a host
String getHostByAddr(byte[] ip) // reverse lookupNameService resides in sun.net.spi.nameservice and is an SPI that InetAddress can load via the Service Provider Interface mechanism.
3. InetAddress Source Code (DNS Part)
The DNS resolution code in InetAddress maintains a static list of NameService implementations. It first tries to load providers defined by the property sun.net.spi.nameservice.provider.. If none are found, a default provider is created.
static {
// create the impl
impl = InetAddressImplFactory.create();
// get name service if provided and requested
String provider = null;
String propPrefix = "sun.net.spi.nameservice.provider.";
int n = 1;
nameServices = new ArrayList<NameService>();
provider = AccessController.doPrivileged(new GetPropertyAction(propPrefix + n));
while (provider != null) {
NameService ns = createNSProvider(provider);
if (ns != null) nameServices.add(ns);
n++;
provider = AccessController.doPrivileged(new GetPropertyAction(propPrefix + n));
}
if (nameServices.size() == 0) {
NameService ns = createNSProvider("default");
nameServices.add(ns);
}
}DNS results are cached inside InetAddress. The cache policy InetAddressCachePolicy.NEVER disables caching.
public CacheEntry get(String host) {
int policy = getPolicy();
// if policy is NEVER, return null (no cache)
if (policy == InetAddressCachePolicy.NEVER) {
return null;
}
CacheEntry entry = cache.get(host);
if (entry != null && policy != InetAddressCachePolicy.FOREVER) {
if (entry.expiration >= 0 && entry.expiration < System.currentTimeMillis()) {
cache.remove(host);
entry = null;
}
}
return entry;
}4. dnsjava Library
dnsjava is a third‑party Java DNS implementation that supports various record types and can act as a mini DNS server or specify custom name servers. The article only mentions it briefly because the solution sometimes uses dnsjava to set an external name server.
Implementation
1. Solution Overview
The solution consists of two steps:
Provide a custom NameService implementation to replace the system default.
Disable DNS caching.
2. Custom NameService (LocalManagedDnsProxy)
@Override
public InetAddress[] lookupAllHostAddr(String name) throws UnknownHostException {
InetAddress ipAddresses = instance.get(name);
if (ipAddresses == null) {
// fallback to default DNS implementation
return defaultDnsImpl.lookupAllHostAddr(name);
}
return new InetAddress[]{ipAddresses};
}3. NameStore Cache
private final Map<String, InetAddress> globalNames = new ConcurrentHashMap<>();
public void put(String hostName, String ipAddress) {
try {
InetAddress ip = InetAddress.getByAddress(DnsUtils.textToNumericFormat(ipAddress));
globalNames.put(hostName, ip);
} catch (UnknownHostException ignored) {}
}
public InetAddress get(String hostName) {
return globalNames.get(hostName);
}
public void remove(String hostName) {
globalNames.remove(hostName);
}4. Using dnsjava to Set an External Name Server
SimpleResolver resolver = new SimpleResolver(nameServer);
Lookup.setDefaultResolver(resolver);5. Injecting the Custom DNS Service into InetAddress
protected void setNameServiceReflectively() {
List<NameService> nameServices = (List<NameService>) ReflectionUtils.getStaticFieldValue(
InetAddress.class, "nameServices");
if (nameServices != null) {
nameServices.add(0, new LocalManagedDnsProxy());
}
}6. Disabling DNS Cache
Two ways exist: setting networkaddress.cache.ttl=0 in java.security, or calling Security.setProperty("networkaddress.cache.ttl", "0") before the cache policy is initialized. The article uses reflection to modify the internal cache policy directly.
private void setCachePolicyReflectively() {
try {
ReflectionUtils.setStaticFieldValue(InetAddressCachePolicy.class, "cachePolicy", 0);
} catch (Exception e) {
logger.error("failed to set cachePolicy", e);
}
}Verification
A JUnit test demonstrates the effect:
@Test
public void testChangeDns() throws UnknownHostException {
// add custom DNS entry
NameStore.getInstance().put("www.baidu.com", "133.3.33.33");
InetAddress inet = InetAddress.getByName("www.baidu.com");
System.out.println("wrong address: " + inet);
// remove custom entry
NameStore.getInstance().remove("www.baidu.com");
inet = InetAddress.getByName("www.baidu.com");
System.out.println("original address: " + inet);
}Console output shows the overridden address (133.3.33.33) and the original address after removal, confirming that dynamic DNS manipulation works.
Conclusion
The article demonstrates how to dynamically replace Java’s default DNS resolver and cache via reflection, enabling flexible DNS handling in automated test cases. With this approach, any test running in the same JVM can redirect domain requests to arbitrary IPs without hard‑coding, simplifying environment management and avoiding DNS conflicts in concurrent Jenkins jobs.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Vipshop Quality Engineering
Technology exchange and sharing for quality engineering
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
