Skip to content

Commit 5da2770

Browse files
chrisruffaloibauersachs
authored andcommitted
Pluggable I/O for SimpleResolver
Closes #253
1 parent 4c51bf2 commit 5da2770

12 files changed

Lines changed: 335 additions & 113 deletions
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
package org.xbill.DNS;
3+
4+
import java.net.InetSocketAddress;
5+
import java.time.Duration;
6+
import java.util.concurrent.CompletableFuture;
7+
import org.xbill.DNS.io.TcpIoClient;
8+
import org.xbill.DNS.io.UdpIoClient;
9+
10+
/**
11+
* An implementation of the IO clients that use the internal NIO-based clients.
12+
*
13+
* @see NioUdpClient
14+
* @see NioTcpClient
15+
* @since 3.6
16+
*/
17+
public class DefaultIoClient implements TcpIoClient, UdpIoClient {
18+
private final TcpIoClient tcpIoClient;
19+
private final UdpIoClient udpIoClient;
20+
21+
public DefaultIoClient() {
22+
tcpIoClient = new NioTcpClient();
23+
udpIoClient = new NioUdpClient();
24+
}
25+
26+
@Override
27+
public CompletableFuture<byte[]> sendAndReceiveTcp(
28+
InetSocketAddress local,
29+
InetSocketAddress remote,
30+
Message query,
31+
byte[] data,
32+
Duration timeout) {
33+
return tcpIoClient.sendAndReceiveTcp(local, remote, query, data, timeout);
34+
}
35+
36+
@Override
37+
public CompletableFuture<byte[]> sendAndReceiveUdp(
38+
InetSocketAddress local,
39+
InetSocketAddress remote,
40+
Message query,
41+
byte[] data,
42+
int max,
43+
Duration timeout) {
44+
return udpIoClient.sendAndReceiveUdp(local, remote, query, data, max, timeout);
45+
}
46+
}

src/main/java/org/xbill/DNS/Lookup.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,8 @@ private static List<Name> convertSearchPathDomainList(List<Name> domains) {
248248
}
249249

250250
/**
251-
* Sets a custom logger that will be used to log the sent and received packets.
251+
* Sets a custom logger that will be used to log the sent and received packets. This is only
252+
* applicable to the default I/O implementations.
252253
*
253254
* @param logger The logger
254255
*/

src/main/java/org/xbill/DNS/NioTcpClient.java

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,21 @@
1818
import java.util.concurrent.ConcurrentLinkedQueue;
1919
import lombok.EqualsAndHashCode;
2020
import lombok.RequiredArgsConstructor;
21-
import lombok.experimental.UtilityClass;
2221
import lombok.extern.slf4j.Slf4j;
22+
import org.xbill.DNS.io.TcpIoClient;
2323

2424
@Slf4j
25-
@UtilityClass
26-
final class NioTcpClient extends NioClient {
27-
private static final Queue<ChannelState> registrationQueue = new ConcurrentLinkedQueue<>();
28-
private static final Map<ChannelKey, ChannelState> channelMap = new ConcurrentHashMap<>();
25+
final class NioTcpClient extends NioClient implements TcpIoClient {
26+
private final Queue<ChannelState> registrationQueue = new ConcurrentLinkedQueue<>();
27+
private final Map<ChannelKey, ChannelState> channelMap = new ConcurrentHashMap<>();
2928

30-
static {
31-
setRegistrationsTask(NioTcpClient::processPendingRegistrations, true);
32-
setTimeoutTask(NioTcpClient::checkTransactionTimeouts, true);
33-
setCloseTask(NioTcpClient::closeTcp, true);
29+
NioTcpClient() {
30+
setRegistrationsTask(this::processPendingRegistrations, true);
31+
setTimeoutTask(this::checkTransactionTimeouts, true);
32+
setCloseTask(this::closeTcp, true);
3433
}
3534

36-
private static void processPendingRegistrations() {
35+
private void processPendingRegistrations() {
3736
while (!registrationQueue.isEmpty()) {
3837
ChannelState state = registrationQueue.remove();
3938
try {
@@ -49,7 +48,7 @@ private static void processPendingRegistrations() {
4948
}
5049
}
5150

52-
private static void checkTransactionTimeouts() {
51+
private void checkTransactionTimeouts() {
5352
for (ChannelState state : channelMap.values()) {
5453
for (Iterator<Transaction> it = state.pendingTransactions.iterator(); it.hasNext(); ) {
5554
Transaction t = it.next();
@@ -61,7 +60,7 @@ private static void checkTransactionTimeouts() {
6160
}
6261
}
6362

64-
private static void closeTcp() {
63+
private void closeTcp() {
6564
registrationQueue.clear();
6665
EOFException closing = new EOFException("Client is closing");
6766
channelMap.forEach((key, state) -> state.handleTransactionException(closing));
@@ -112,8 +111,8 @@ void send() throws IOException {
112111
}
113112

114113
@RequiredArgsConstructor
115-
private static class ChannelState implements KeyProcessor {
116-
final SocketChannel channel;
114+
private class ChannelState implements KeyProcessor {
115+
private final SocketChannel channel;
117116
final Queue<Transaction> pendingTransactions = new ConcurrentLinkedQueue<>();
118117
ByteBuffer responseLengthData = ByteBuffer.allocate(2);
119118
ByteBuffer responseData = ByteBuffer.allocate(Message.MAXLENGTH);
@@ -259,7 +258,8 @@ private static class ChannelKey {
259258
final InetSocketAddress remote;
260259
}
261260

262-
static CompletableFuture<byte[]> sendrecv(
261+
@Override
262+
public CompletableFuture<byte[]> sendAndReceiveTcp(
263263
InetSocketAddress local,
264264
InetSocketAddress remote,
265265
Message query,

src/main/java/org/xbill/DNS/NioUdpClient.java

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,19 @@
1717
import java.util.concurrent.CompletableFuture;
1818
import java.util.concurrent.ConcurrentLinkedQueue;
1919
import lombok.RequiredArgsConstructor;
20-
import lombok.experimental.UtilityClass;
2120
import lombok.extern.slf4j.Slf4j;
21+
import org.xbill.DNS.io.UdpIoClient;
2222

2323
@Slf4j
24-
@UtilityClass
25-
final class NioUdpClient extends NioClient {
26-
private static final int EPHEMERAL_START;
27-
private static final int EPHEMERAL_RANGE;
24+
final class NioUdpClient extends NioClient implements UdpIoClient {
25+
private final int ephemeralStart;
26+
private final int ephemeralRange;
2827

29-
private static final SecureRandom prng;
30-
private static final Queue<Transaction> registrationQueue = new ConcurrentLinkedQueue<>();
31-
private static final Queue<Transaction> pendingTransactions = new ConcurrentLinkedQueue<>();
28+
private final SecureRandom prng;
29+
private final Queue<Transaction> registrationQueue = new ConcurrentLinkedQueue<>();
30+
private final Queue<Transaction> pendingTransactions = new ConcurrentLinkedQueue<>();
3231

33-
static {
32+
NioUdpClient() {
3433
// https://tools.ietf.org/html/rfc6335#section-6
3534
int ephemeralStartDefault = 49152;
3635
int ephemeralEndDefault = 65535;
@@ -41,21 +40,21 @@ final class NioUdpClient extends NioClient {
4140
ephemeralEndDefault = 60999;
4241
}
4342

44-
EPHEMERAL_START = Integer.getInteger("dnsjava.udp.ephemeral.start", ephemeralStartDefault);
43+
ephemeralStart = Integer.getInteger("dnsjava.udp.ephemeral.start", ephemeralStartDefault);
4544
int ephemeralEnd = Integer.getInteger("dnsjava.udp.ephemeral.end", ephemeralEndDefault);
46-
EPHEMERAL_RANGE = ephemeralEnd - EPHEMERAL_START;
45+
ephemeralRange = ephemeralEnd - ephemeralStart;
4746

4847
if (Boolean.getBoolean("dnsjava.udp.ephemeral.use_ephemeral_port")) {
4948
prng = null;
5049
} else {
5150
prng = new SecureRandom();
5251
}
53-
setRegistrationsTask(NioUdpClient::processPendingRegistrations, false);
54-
setTimeoutTask(NioUdpClient::checkTransactionTimeouts, false);
55-
setCloseTask(NioUdpClient::closeUdp, false);
52+
setRegistrationsTask(this::processPendingRegistrations, false);
53+
setTimeoutTask(this::checkTransactionTimeouts, false);
54+
setCloseTask(this::closeUdp, false);
5655
}
5756

58-
private static void processPendingRegistrations() {
57+
private void processPendingRegistrations() {
5958
while (!registrationQueue.isEmpty()) {
6059
Transaction t = registrationQueue.remove();
6160
try {
@@ -68,7 +67,7 @@ private static void processPendingRegistrations() {
6867
}
6968
}
7069

71-
private static void checkTransactionTimeouts() {
70+
private void checkTransactionTimeouts() {
7271
for (Iterator<Transaction> it = pendingTransactions.iterator(); it.hasNext(); ) {
7372
Transaction t = it.next();
7473
if (t.endTime - System.nanoTime() < 0) {
@@ -79,7 +78,7 @@ private static void checkTransactionTimeouts() {
7978
}
8079

8180
@RequiredArgsConstructor
82-
private static class Transaction implements KeyProcessor {
81+
private class Transaction implements KeyProcessor {
8382
private final int id;
8483
private final byte[] data;
8584
private final int max;
@@ -159,7 +158,8 @@ private void silentCloseChannel() {
159158
}
160159
}
161160

162-
static CompletableFuture<byte[]> sendrecv(
161+
@Override
162+
public CompletableFuture<byte[]> sendAndReceiveUdp(
163163
InetSocketAddress local,
164164
InetSocketAddress remote,
165165
Message query,
@@ -182,12 +182,12 @@ static CompletableFuture<byte[]> sendrecv(
182182
InetSocketAddress addr = null;
183183
if (local == null) {
184184
if (prng != null) {
185-
addr = new InetSocketAddress(prng.nextInt(EPHEMERAL_RANGE) + EPHEMERAL_START);
185+
addr = new InetSocketAddress(prng.nextInt(ephemeralRange) + ephemeralStart);
186186
}
187187
} else {
188188
int port = local.getPort();
189189
if (port == 0 && prng != null) {
190-
port = prng.nextInt(EPHEMERAL_RANGE) + EPHEMERAL_START;
190+
port = prng.nextInt(ephemeralRange) + ephemeralStart;
191191
}
192192

193193
addr = new InetSocketAddress(local.getAddress(), port);
@@ -225,7 +225,7 @@ static CompletableFuture<byte[]> sendrecv(
225225
return f;
226226
}
227227

228-
private static void closeUdp() {
228+
private void closeUdp() {
229229
registrationQueue.clear();
230230
EOFException closing = new EOFException("Client is closing");
231231
pendingTransactions.forEach(t -> t.completeExceptionally(closing));

src/main/java/org/xbill/DNS/SimpleResolver.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414
import java.util.concurrent.CompletionStage;
1515
import java.util.concurrent.Executor;
1616
import java.util.concurrent.ForkJoinPool;
17+
import lombok.Getter;
18+
import lombok.Setter;
1719
import lombok.extern.slf4j.Slf4j;
20+
import org.xbill.DNS.io.DefaultIoClientFactory;
21+
import org.xbill.DNS.io.IoClientFactory;
1822

1923
/**
2024
* An implementation of Resolver that sends one query to one server. SimpleResolver handles TCP
@@ -44,6 +48,13 @@ public class SimpleResolver implements Resolver {
4448

4549
private static final short DEFAULT_UDPSIZE = 512;
4650

51+
/**
52+
* Gets or sets the factory that creates clients for sending messages to the wire.
53+
*
54+
* @since 3.6
55+
*/
56+
@Getter @Setter private IoClientFactory ioClientFactory = new DefaultIoClientFactory();
57+
4758
private static InetSocketAddress defaultResolver =
4859
new InetSocketAddress(InetAddress.getLoopbackAddress(), DEFAULT_PORT);
4960

@@ -368,9 +379,15 @@ CompletableFuture<Message> sendAsync(Message query, boolean forceTcp, Executor e
368379

369380
CompletableFuture<byte[]> result;
370381
if (tcp) {
371-
result = NioTcpClient.sendrecv(localAddress, address, query, out, timeoutValue);
382+
result =
383+
ioClientFactory
384+
.createOrGetTcpClient()
385+
.sendAndReceiveTcp(localAddress, address, query, out, timeoutValue);
372386
} else {
373-
result = NioUdpClient.sendrecv(localAddress, address, query, out, udpSize, timeoutValue);
387+
result =
388+
ioClientFactory
389+
.createOrGetUdpClient()
390+
.sendAndReceiveUdp(localAddress, address, query, out, udpSize, timeoutValue);
374391
}
375392

376393
return result.thenComposeAsync(
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
package org.xbill.DNS.io;
3+
4+
import org.xbill.DNS.DefaultIoClient;
5+
import org.xbill.DNS.SimpleResolver;
6+
7+
/**
8+
* Serves as a default implementation that is used by the {@link SimpleResolver}, unless otherwise
9+
* configured. This preserves the default behavior (to use the built-in NIO clients) while allowing
10+
* flexibility at the point of use.
11+
*
12+
* @since 3.6
13+
*/
14+
public class DefaultIoClientFactory implements IoClientFactory {
15+
/**
16+
* Shared instance because it only serves as a bridge to the static NIO classes and does not need
17+
* to be different per class.
18+
*/
19+
private static final DefaultIoClient RESOLVER_CLIENT = new DefaultIoClient();
20+
21+
@Override
22+
public TcpIoClient createOrGetTcpClient() {
23+
return RESOLVER_CLIENT;
24+
}
25+
26+
@Override
27+
public UdpIoClient createOrGetUdpClient() {
28+
return RESOLVER_CLIENT;
29+
}
30+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
package org.xbill.DNS.io;
3+
4+
import org.xbill.DNS.SimpleResolver;
5+
6+
/**
7+
* Interface for creating the TCP/UDP factories necessary for the {@link SimpleResolver}.
8+
*
9+
* @since 3.6
10+
*/
11+
public interface IoClientFactory {
12+
/**
13+
* Create or return a cached/reused instance of the TCP resolver that should be used to send DNS
14+
* data over the wire to the remote target. <br>
15+
* It is the responsibility of this method to manage pooling or connection reuse. This method is
16+
* called right before the connection is made every time the {@link SimpleResolver} is called. The
17+
* implementer of this method should be aware and choose how to pool or reuse connections.
18+
*
19+
* @return an instance of the tcp resolver client
20+
*/
21+
TcpIoClient createOrGetTcpClient();
22+
23+
/**
24+
* Create or return a cached/reused instance of the UDP resolver that should be used to send DNS
25+
* data over the wire to the remote target.
26+
*
27+
* @return an instance of the udp resolver client
28+
*/
29+
UdpIoClient createOrGetUdpClient();
30+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
package org.xbill.DNS.io;
3+
4+
import java.net.InetSocketAddress;
5+
import java.time.Duration;
6+
import java.util.concurrent.CompletableFuture;
7+
import org.xbill.DNS.Message;
8+
import org.xbill.DNS.Resolver;
9+
10+
/**
11+
* Serves as an interface from a {@link Resolver} to the underlying mechanism for sending bytes over
12+
* the wire as a TCP message.
13+
*
14+
* @since 3.6
15+
*/
16+
public interface TcpIoClient {
17+
/**
18+
* Sends a query to a remote server and returns the answer.
19+
*
20+
* @param local Address from which the connection is coming. may be {@code null} and the
21+
* implementation must decide on the local address.
22+
* @param remote Address that the connection should send the data to.
23+
* @param query DNS message representation of the outbound query.
24+
* @param data Raw byte representation of the outbound query.
25+
* @param timeout Duration before the connection will time out and be closed.
26+
* @return A {@link CompletableFuture} that will be completed with the byte value of the response.
27+
* @since 3.6
28+
*/
29+
CompletableFuture<byte[]> sendAndReceiveTcp(
30+
InetSocketAddress local,
31+
InetSocketAddress remote,
32+
Message query,
33+
byte[] data,
34+
Duration timeout);
35+
}

0 commit comments

Comments
 (0)