Skip to content

Commit fbe59f6

Browse files
authored
Merge pull request #67 from tls-attacker/fix/dns_resolution_in_testUnknownHost
Add DNS resolver abstraction and mock for testable hostname resolution
2 parents 2d471d5 + a9657d6 commit fbe59f6

File tree

5 files changed

+148
-11
lines changed

5 files changed

+148
-11
lines changed

src/main/java/de/rub/nds/crawler/data/ScanTarget.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010

1111
import de.rub.nds.crawler.constant.JobStatus;
1212
import de.rub.nds.crawler.denylist.IDenylistProvider;
13+
import de.rub.nds.crawler.dns.DefaultDnsResolver;
14+
import de.rub.nds.crawler.dns.DnsResolver;
1315
import java.io.Serializable;
14-
import java.net.InetAddress;
1516
import java.net.UnknownHostException;
1617
import org.apache.commons.lang3.tuple.Pair;
1718
import org.apache.commons.validator.routines.InetAddressValidator;
@@ -36,6 +37,25 @@ public class ScanTarget implements Serializable {
3637
*/
3738
public static Pair<ScanTarget, JobStatus> fromTargetString(
3839
String targetString, int defaultPort, IDenylistProvider denylistProvider) {
40+
return fromTargetString(
41+
targetString, defaultPort, denylistProvider, new DefaultDnsResolver());
42+
}
43+
44+
/**
45+
* Initializes a ScanTarget object from a string that potentially contains a hostname, an ip, a
46+
* port, the tranco rank.
47+
*
48+
* @param targetString from which to create the ScanTarget object
49+
* @param defaultPort that used if no port is present in targetString
50+
* @param denylistProvider which provides info if a host is denylisted
51+
* @param dnsResolver the DNS resolver to use for hostname resolution
52+
* @return ScanTarget object
53+
*/
54+
public static Pair<ScanTarget, JobStatus> fromTargetString(
55+
String targetString,
56+
int defaultPort,
57+
IDenylistProvider denylistProvider,
58+
DnsResolver dnsResolver) {
3959
ScanTarget target = new ScanTarget();
4060

4161
// check if targetString contains rank (e.g. "1,example.com")
@@ -55,8 +75,7 @@ public static Pair<ScanTarget, JobStatus> fromTargetString(
5575
targetString = parts[1];
5676
if (targetString.trim().isEmpty()) {
5777
try {
58-
target.setIp(
59-
InetAddress.getByName(target.getHostname()).getHostAddress());
78+
target.setIp(dnsResolver.resolveHostname(target.getHostname()));
6079
} catch (UnknownHostException e) {
6180
return Pair.of(target, JobStatus.UNRESOLVABLE);
6281
}
@@ -124,7 +143,7 @@ public static Pair<ScanTarget, JobStatus> fromTargetString(
124143
try {
125144
// TODO this only allows one IP per hostname; it may be interesting to scan all IPs
126145
// for a domain, or at least one v4 and one v6
127-
target.setIp(InetAddress.getByName(targetString).getHostAddress());
146+
target.setIp(dnsResolver.resolveHostname(targetString));
128147
} catch (UnknownHostException e) {
129148
LOGGER.error(
130149
"Host {} is unknown or can not be reached with error {}.", targetString, e);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* TLS-Crawler - A TLS scanning tool to perform large scale scans with the TLS-Scanner
3+
*
4+
* Copyright 2018-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
5+
*
6+
* Licensed under Apache License, Version 2.0
7+
* http://www.apache.org/licenses/LICENSE-2.0.txt
8+
*/
9+
package de.rub.nds.crawler.dns;
10+
11+
import java.net.InetAddress;
12+
import java.net.UnknownHostException;
13+
14+
/**
15+
* Default DNS resolver implementation that uses the standard Java DNS resolution via InetAddress.
16+
*/
17+
public class DefaultDnsResolver implements DnsResolver {
18+
19+
/** Creates a new default DNS resolver instance. */
20+
public DefaultDnsResolver() {}
21+
22+
@Override
23+
public String resolveHostname(String hostname) throws UnknownHostException {
24+
return InetAddress.getByName(hostname).getHostAddress();
25+
}
26+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* TLS-Crawler - A TLS scanning tool to perform large scale scans with the TLS-Scanner
3+
*
4+
* Copyright 2018-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
5+
*
6+
* Licensed under Apache License, Version 2.0
7+
* http://www.apache.org/licenses/LICENSE-2.0.txt
8+
*/
9+
package de.rub.nds.crawler.dns;
10+
11+
import java.net.UnknownHostException;
12+
13+
/** Interface for DNS resolution. Allows for mocking and testing of DNS lookups. */
14+
public interface DnsResolver {
15+
/**
16+
* Resolves a hostname to its IP address.
17+
*
18+
* @param hostname the hostname to resolve
19+
* @return the IP address as a string
20+
* @throws UnknownHostException if the hostname cannot be resolved
21+
*/
22+
String resolveHostname(String hostname) throws UnknownHostException;
23+
}

src/test/java/de/rub/nds/crawler/data/ScanTargetTest.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import static org.junit.jupiter.api.Assertions.*;
1212

1313
import de.rub.nds.crawler.constant.JobStatus;
14+
import de.rub.nds.crawler.dns.MockDnsResolver;
1415
import org.apache.commons.lang3.tuple.Pair;
1516
import org.junit.jupiter.api.Test;
1617

@@ -76,22 +77,28 @@ void testIPv6CompressedAddress() {
7677

7778
@Test
7879
void testHostnameWithPort() {
80+
MockDnsResolver mockDnsResolver = new MockDnsResolver();
81+
mockDnsResolver.addMapping("example.com", "93.184.216.34");
82+
7983
Pair<ScanTarget, JobStatus> result =
80-
ScanTarget.fromTargetString("example.com:8080", 443, null);
84+
ScanTarget.fromTargetString("example.com:8080", 443, null, mockDnsResolver);
8185
assertEquals(JobStatus.TO_BE_EXECUTED, result.getRight());
8286
assertEquals("example.com", result.getLeft().getHostname());
8387
assertEquals(8080, result.getLeft().getPort());
84-
// IP will be resolved by DNS lookup, so we can't test the exact value
85-
assertNotNull(result.getLeft().getIp());
88+
assertEquals("93.184.216.34", result.getLeft().getIp());
8689
}
8790

8891
@Test
8992
void testHostnameWithoutPort() {
90-
Pair<ScanTarget, JobStatus> result = ScanTarget.fromTargetString("example.com", 443, null);
93+
MockDnsResolver mockDnsResolver = new MockDnsResolver();
94+
mockDnsResolver.addMapping("example.com", "93.184.216.34");
95+
96+
Pair<ScanTarget, JobStatus> result =
97+
ScanTarget.fromTargetString("example.com", 443, null, mockDnsResolver);
9198
assertEquals(JobStatus.TO_BE_EXECUTED, result.getRight());
9299
assertEquals("example.com", result.getLeft().getHostname());
93100
assertEquals(443, result.getLeft().getPort());
94-
assertNotNull(result.getLeft().getIp());
101+
assertEquals("93.184.216.34", result.getLeft().getIp());
95102
}
96103

97104
@Test
@@ -143,10 +150,14 @@ void testTrancoRankWithIPv6() {
143150

144151
@Test
145152
void testUnknownHost() {
153+
MockDnsResolver mockDnsResolver = new MockDnsResolver();
154+
mockDnsResolver.addUnresolvableHost("this-host-should-not-exist.invalid");
155+
146156
Pair<ScanTarget, JobStatus> result =
147-
ScanTarget.fromTargetString("this-host-should-not-exist-12345.com", 443, null);
157+
ScanTarget.fromTargetString(
158+
"this-host-should-not-exist.invalid", 443, null, mockDnsResolver);
148159
assertEquals(JobStatus.UNRESOLVABLE, result.getRight());
149-
assertEquals("this-host-should-not-exist-12345.com", result.getLeft().getHostname());
160+
assertEquals("this-host-should-not-exist.invalid", result.getLeft().getHostname());
150161
assertNull(result.getLeft().getIp());
151162
}
152163

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* TLS-Crawler - A TLS scanning tool to perform large scale scans with the TLS-Scanner
3+
*
4+
* Copyright 2018-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
5+
*
6+
* Licensed under Apache License, Version 2.0
7+
* http://www.apache.org/licenses/LICENSE-2.0.txt
8+
*/
9+
package de.rub.nds.crawler.dns;
10+
11+
import java.net.UnknownHostException;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
15+
/**
16+
* Mock DNS resolver for testing purposes. Allows configuring hostname-to-IP mappings and
17+
* controlling whether a hostname should throw an UnknownHostException.
18+
*/
19+
public class MockDnsResolver implements DnsResolver {
20+
21+
private final Map<String, String> hostnameToIpMap = new HashMap<>();
22+
private final Map<String, Boolean> hostnameToUnresolvableMap = new HashMap<>();
23+
24+
/**
25+
* Adds a mapping from hostname to IP address.
26+
*
27+
* @param hostname the hostname to map
28+
* @param ipAddress the IP address to return for this hostname
29+
*/
30+
public void addMapping(String hostname, String ipAddress) {
31+
hostnameToIpMap.put(hostname, ipAddress);
32+
hostnameToUnresolvableMap.put(hostname, false);
33+
}
34+
35+
/**
36+
* Configures a hostname to throw UnknownHostException when resolved.
37+
*
38+
* @param hostname the hostname that should be unresolvable
39+
*/
40+
public void addUnresolvableHost(String hostname) {
41+
hostnameToUnresolvableMap.put(hostname, true);
42+
}
43+
44+
@Override
45+
public String resolveHostname(String hostname) throws UnknownHostException {
46+
if (hostnameToUnresolvableMap.getOrDefault(hostname, false)) {
47+
throw new UnknownHostException("Mock: hostname is unresolvable: " + hostname);
48+
}
49+
50+
String ipAddress = hostnameToIpMap.get(hostname);
51+
if (ipAddress != null) {
52+
return ipAddress;
53+
}
54+
55+
// If no mapping is found, throw UnknownHostException
56+
throw new UnknownHostException("Mock: no mapping configured for hostname: " + hostname);
57+
}
58+
}

0 commit comments

Comments
 (0)