Skip to content

Commit fe52e47

Browse files
committed
Bound DNS lookups with raw UDP resolver
1 parent d80c66a commit fe52e47

3 files changed

Lines changed: 343 additions & 68 deletions

File tree

net/src/net/org/jnode/net/ipv4/util/ResolverImpl.java

Lines changed: 220 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020

2121
package org.jnode.net.ipv4.util;
2222

23+
import java.io.InterruptedIOException;
24+
import java.io.IOException;
25+
import java.net.DatagramPacket;
26+
import java.net.DatagramSocket;
27+
import java.net.InetAddress;
2328
import java.net.UnknownHostException;
2429
import java.security.AccessController;
2530
import java.security.PrivilegedAction;
@@ -28,17 +33,25 @@
2833
import java.util.HashMap;
2934
import java.util.Map;
3035
import java.util.Collection;
36+
import java.util.Vector;
3137

3238
import org.jnode.driver.net.NetworkException;
3339
import org.jnode.net.ProtocolAddress;
3440
import org.jnode.net.Resolver;
3541
import org.jnode.net.ipv4.IPv4Address;
3642
import org.jnode.annotation.SharedStatics;
43+
import org.xbill.DNS.DClass;
3744
import org.xbill.DNS.ExtendedResolver;
45+
import org.xbill.DNS.Flags;
3846
import org.xbill.DNS.Lookup;
47+
import org.xbill.DNS.Message;
48+
import org.xbill.DNS.Name;
49+
import org.xbill.DNS.Rcode;
3950
import org.xbill.DNS.Record;
51+
import org.xbill.DNS.Section;
4052
import org.xbill.DNS.SimpleResolver;
4153
import org.xbill.DNS.TextParseException;
54+
import org.xbill.DNS.Type;
4255

4356
/**
4457
* @author Martin Hartvig
@@ -50,13 +63,17 @@ public class ResolverImpl implements Resolver {
5063
// FIXME ... this class looks like it is supposed to implement
5164
// the Singleton pattern. So how come the management methods
5265
// and a lot of the state is 'static'?
53-
private static final int DNS_TIMEOUT_SECONDS = 5;
54-
private static final int DNS_RETRIES = 1;
66+
private static final int DNS_TIMEOUT_SECONDS = 1;
67+
private static final int DNS_RETRIES = 0;
68+
private static final int DNS_LOOKUP_TIMEOUT_MILLIS = 1000;
69+
private static final int DNS_SERVER_PORT = 53;
5570

5671
private static ExtendedResolver resolver;
5772

5873
private static Map<String, org.xbill.DNS.Resolver> resolvers;
5974

75+
private static Vector<String> dnsServers;
76+
6077
private static Map<String, ProtocolAddress[]> hosts;
6178

6279
private static Resolver res = null;
@@ -68,6 +85,7 @@ public class ResolverImpl implements Resolver {
6885
ProtocolAddress[] protocolAddresses = new ProtocolAddress[] {new IPv4Address("127.0.0.1")};
6986
hosts.put(localhost, protocolAddresses);
7087
resolvers = new HashMap<String, org.xbill.DNS.Resolver>();
88+
dnsServers = new Vector<String>();
7189
}
7290

7391
private ResolverImpl() {
@@ -97,7 +115,9 @@ public Object run() {
97115
* Get list all the dns servers
98116
*/
99117
public static Collection<String> getDnsServers() {
100-
return resolvers.keySet();
118+
synchronized (ResolverImpl.class) {
119+
return new Vector<String>(resolvers.keySet());
120+
}
101121
}
102122

103123
/**
@@ -107,42 +127,43 @@ public static Collection<String> getDnsServers() {
107127
* @throws NetworkException
108128
*/
109129
public static void addDnsServer(ProtocolAddress _dnsserver) throws NetworkException {
110-
try {
111-
if (resolver == null) {
112-
final String[] server = new String[] {_dnsserver.toString()};
113-
try {
114-
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
115-
public Object run() throws Exception {
116-
resolver = new ExtendedResolver(server);
117-
configureResolver(resolver);
118-
Lookup.setDefaultResolver(resolver);
119-
return null;
130+
final String key = _dnsserver.toString();
131+
synchronized (ResolverImpl.class) {
132+
try {
133+
if (resolver == null) {
134+
try {
135+
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
136+
public Object run() throws Exception {
137+
resolver = new ExtendedResolver(new String[] {key});
138+
configureResolver(resolver);
139+
Lookup.setDefaultResolver(resolver);
140+
return null;
141+
}
142+
});
143+
} catch (PrivilegedActionException x) {
144+
Exception ee = x.getException();
145+
if (ee instanceof UnknownHostException) {
146+
throw (UnknownHostException) ee;
147+
} else {
148+
throw new RuntimeException(ee);
120149
}
121-
});
122-
} catch (PrivilegedActionException x) {
123-
Exception ee = x.getException();
124-
if (ee instanceof UnknownHostException) {
125-
throw (UnknownHostException) ee;
126-
} else {
127-
throw new RuntimeException(ee);
128150
}
151+
dnsServers.clear();
152+
dnsServers.add(key);
153+
resolvers.put(key, resolver);
154+
return;
129155
}
130-
resolvers.put(_dnsserver.toString(), resolver);
131-
}
132-
} catch (UnknownHostException e) {
133-
throw new NetworkException("Can't add DNS server", e);
134-
}
135156

136-
try {
137-
String key = _dnsserver.toString();
138-
if (!resolvers.containsKey(key)) {
139-
SimpleResolver simpleResolver = new SimpleResolver(key);
140-
resolver.addResolver(simpleResolver);
141-
configureResolver(resolver);
142-
resolvers.put(key, simpleResolver);
157+
if (!resolvers.containsKey(key)) {
158+
SimpleResolver simpleResolver = new SimpleResolver(key);
159+
resolver.addResolver(simpleResolver);
160+
configureResolver(resolver);
161+
resolvers.put(key, simpleResolver);
162+
dnsServers.add(key);
163+
}
164+
} catch (UnknownHostException e) {
165+
throw new NetworkException("Can't add DNS server", e);
143166
}
144-
} catch (UnknownHostException e) {
145-
throw new NetworkException("Can't add DNS server", e);
146167
}
147168
}
148169

@@ -152,23 +173,165 @@ public Object run() throws Exception {
152173
* @param _dnsserver
153174
*/
154175
public static void removeDnsServer(ProtocolAddress _dnsserver) {
155-
if (resolver == null) {
156-
return;
157-
}
158176
String key = _dnsserver.toString();
159-
if (resolvers.containsKey(key)) {
160-
org.xbill.DNS.Resolver resolv = resolvers.remove(key);
161-
if (resolver.getResolvers().length == 1) {
162-
resolver = null;
163-
} else {
164-
resolver.deleteResolver(resolv);
177+
synchronized (ResolverImpl.class) {
178+
if (resolver == null) {
179+
return;
180+
}
181+
if (resolvers.containsKey(key)) {
182+
org.xbill.DNS.Resolver resolv = resolvers.remove(key);
183+
dnsServers.remove(key);
184+
if (resolver.getResolvers().length == 1) {
185+
resolver = null;
186+
dnsServers.clear();
187+
} else {
188+
resolver.deleteResolver(resolv);
189+
}
190+
}
191+
}
192+
}
193+
194+
static void configureResolver(ExtendedResolver resolver) {
195+
configureResolver(resolver, DNS_TIMEOUT_SECONDS, DNS_RETRIES);
196+
}
197+
198+
static void configureResolver(ExtendedResolver resolver, int timeoutSeconds, int retries) {
199+
resolver.setTimeout(timeoutSeconds);
200+
resolver.setRetries(retries);
201+
}
202+
203+
static Record[] resolve(final String hostname, int timeoutMillis) throws UnknownHostException {
204+
final String[] servers;
205+
synchronized (ResolverImpl.class) {
206+
if ((dnsServers == null) || dnsServers.isEmpty()) {
207+
throw new UnknownHostException("No DNS server configured");
208+
}
209+
servers = dnsServers.toArray(new String[dnsServers.size()]);
210+
}
211+
UnknownHostException lastException = null;
212+
for (String server : servers) {
213+
try {
214+
return resolveFromServer(hostname, server, timeoutMillis);
215+
} catch (UnknownHostException ex) {
216+
lastException = ex;
217+
}
218+
}
219+
if (lastException != null) {
220+
throw lastException;
221+
}
222+
throw new UnknownHostException("DNS lookup failed");
223+
}
224+
225+
private static Record[] resolveFromServer(String hostname, String server, int timeoutMillis)
226+
throws UnknownHostException {
227+
try {
228+
String dnsName = hostname.endsWith(".") ? hostname : hostname + ".";
229+
Name name = Name.fromString(dnsName);
230+
Record question = Record.newRecord(name, Type.A, DClass.IN);
231+
Message query = Message.newQuery(question);
232+
query.getHeader().setFlag(Flags.RD);
233+
byte[] response = sendUdpWithCloseTimeout(InetAddress.getByName(server), DNS_SERVER_PORT,
234+
query.toWire(), timeoutMillis);
235+
Message responseMessage = new Message(response);
236+
if (responseMessage.getHeader().getID() != query.getHeader().getID()) {
237+
throw new UnknownHostException("Invalid DNS response id");
238+
}
239+
int rcode = responseMessage.getRcode();
240+
if (rcode != Rcode.NOERROR) {
241+
throw new UnknownHostException("DNS lookup failed: " + Rcode.string(rcode));
165242
}
243+
Record[] answers = responseMessage.getSectionArray(Section.ANSWER);
244+
if ((answers == null) || (answers.length == 0)) {
245+
throw new UnknownHostException("No DNS answer for " + hostname);
246+
}
247+
return answers;
248+
} catch (TextParseException ex) {
249+
throw new UnknownHostException(hostname);
250+
} catch (IOException ex) {
251+
throw new UnknownHostException(ex.getMessage());
166252
}
167253
}
168254

169-
private static void configureResolver(ExtendedResolver resolver) {
170-
resolver.setTimeout(DNS_TIMEOUT_SECONDS);
171-
resolver.setRetries(DNS_RETRIES);
255+
static byte[] sendUdpWithCloseTimeout(final InetAddress address, final int port,
256+
final byte[] request, final int timeoutMillis) throws IOException {
257+
final byte[] requestCopy = copy(request, 0, request.length);
258+
final UdpResult result = new UdpResult();
259+
Thread lookupThread = new Thread(new Runnable() {
260+
public void run() {
261+
DatagramSocket socket = null;
262+
try {
263+
socket = new DatagramSocket();
264+
result.socket = socket;
265+
socket.setSoTimeout(timeoutMillis);
266+
DatagramPacket requestPacket = new DatagramPacket(requestCopy, requestCopy.length, address, port);
267+
socket.send(requestPacket);
268+
byte[] buffer = new byte[512];
269+
DatagramPacket responsePacket = new DatagramPacket(buffer, buffer.length);
270+
socket.receive(responsePacket);
271+
result.data = copy(responsePacket.getData(), 0, responsePacket.getLength());
272+
} catch (Throwable t) {
273+
result.throwable = t;
274+
} finally {
275+
if (socket != null) {
276+
socket.close();
277+
}
278+
}
279+
}
280+
});
281+
lookupThread.setDaemon(true);
282+
lookupThread.start();
283+
long deadline = System.currentTimeMillis() + timeoutMillis;
284+
try {
285+
while (lookupThread.isAlive()) {
286+
long remaining = deadline - System.currentTimeMillis();
287+
if (remaining <= 0) {
288+
break;
289+
}
290+
Thread.sleep(Math.min(50, remaining));
291+
}
292+
} catch (InterruptedException e) {
293+
Thread.currentThread().interrupt();
294+
if (result.socket != null) {
295+
result.socket.close();
296+
}
297+
throw new InterruptedIOException("DNS lookup interrupted");
298+
}
299+
if (lookupThread.isAlive()) {
300+
if (result.socket != null) {
301+
result.socket.close();
302+
}
303+
try {
304+
lookupThread.join(100);
305+
} catch (InterruptedException e) {
306+
Thread.currentThread().interrupt();
307+
}
308+
throw new InterruptedIOException("DNS lookup timed out");
309+
}
310+
if (result.throwable instanceof Error) {
311+
throw (Error) result.throwable;
312+
}
313+
if (result.throwable instanceof RuntimeException) {
314+
throw (RuntimeException) result.throwable;
315+
}
316+
if (result.throwable instanceof IOException) {
317+
throw (IOException) result.throwable;
318+
}
319+
if (result.throwable != null) {
320+
throw new IOException(result.throwable.getMessage());
321+
}
322+
return result.data;
323+
}
324+
325+
private static byte[] copy(byte[] source, int offset, int length) {
326+
byte[] copy = new byte[length];
327+
System.arraycopy(source, offset, copy, 0, length);
328+
return copy;
329+
}
330+
331+
private static class UdpResult {
332+
private volatile DatagramSocket socket;
333+
private volatile byte[] data;
334+
private volatile Throwable throwable;
172335
}
173336

174337
/**
@@ -198,7 +361,11 @@ public ProtocolAddress[] getByName(final String hostname) throws UnknownHostExce
198361
// FIXME ... why is this a special case? Comment please or fix it.
199362
throw new UnknownHostException("*");
200363
}
201-
if (resolver == null) {
364+
final ExtendedResolver dnsResolver;
365+
synchronized (ResolverImpl.class) {
366+
dnsResolver = resolver;
367+
}
368+
if (dnsResolver == null) {
202369
throw new UnknownHostException(hostname);
203370
}
204371

@@ -215,29 +382,14 @@ public ProtocolAddress[] run() throws UnknownHostException {
215382
return protocolAddresses;
216383
}
217384

218-
Lookup.setDefaultResolver(resolver);
219-
220-
final Lookup lookup;
221-
try {
222-
lookup = new Lookup(hostname);
223-
} catch (TextParseException e) {
224-
throw new UnknownHostException(hostname);
225-
}
226-
227-
lookup.run();
385+
final Record[] records = resolve(hostname, DNS_LOOKUP_TIMEOUT_MILLIS);
386+
final int recordCount = records.length;
228387

229-
if (lookup.getResult() == Lookup.SUCCESSFUL) {
230-
final Record[] records = lookup.getAnswers();
231-
final int recordCount = records.length;
388+
protocolAddresses = new ProtocolAddress[recordCount];
232389

233-
protocolAddresses = new ProtocolAddress[recordCount];
234-
235-
for (int i = 0; i < recordCount; i++) {
236-
final Record record = records[i];
237-
protocolAddresses[i] = new IPv4Address(record.rdataToString());
238-
}
239-
} else {
240-
throw new UnknownHostException(lookup.getErrorString());
390+
for (int i = 0; i < recordCount; i++) {
391+
final Record record = records[i];
392+
protocolAddresses[i] = new IPv4Address(record.rdataToString());
241393
}
242394

243395
return protocolAddresses;

net/src/test/org/jnode/net/AllTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.jnode.net.ipv4.icmp.ICMPTypeTest;
2626
import org.jnode.net.ipv4.tcp.TCPInChannelTest;
2727
import org.jnode.net.ipv4.tcp.TCPOutChannelTest;
28+
import org.jnode.net.ipv4.util.ResolverImplTest;
2829
import org.junit.runner.RunWith;
2930
import org.junit.runners.Suite;
3031

@@ -33,6 +34,7 @@
3334
TCPInChannelTest.class, //
3435
TCPOutChannelTest.class, //
3536
ICMPTypeTest.class, //
37+
ResolverImplTest.class, //
3638
ARPHeaderTest.class, //
3739
EthernetAddressTest.class })
3840
public class AllTests {

0 commit comments

Comments
 (0)