001: /*
002: * Copyright 2000-2004 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.jndi.dns;
027:
028: import java.io.IOException;
029: import java.net.DatagramSocket;
030: import java.net.DatagramPacket;
031: import java.net.InetAddress;
032: import java.net.Socket;
033: import javax.naming.*;
034:
035: import java.util.Collections;
036: import java.util.Map;
037: import java.util.HashMap;
038: import java.util.Set;
039: import java.util.HashSet;
040:
041: // Some of this code began life as part of sun.javaos.net.DnsClient
042: // originally by sritchie@eng 1/96. It was first hacked up for JNDI
043: // use by caveh@eng 6/97.
044:
045: /**
046: * The DnsClient class performs DNS client operations in support of DnsContext.
047: *
048: * @version 1.27 07/05/05
049: */
050:
051: public class DnsClient {
052:
053: // DNS packet header field offsets
054: private static final int IDENT_OFFSET = 0;
055: private static final int FLAGS_OFFSET = 2;
056: private static final int NUMQ_OFFSET = 4;
057: private static final int NUMANS_OFFSET = 6;
058: private static final int NUMAUTH_OFFSET = 8;
059: private static final int NUMADD_OFFSET = 10;
060: private static final int DNS_HDR_SIZE = 12;
061:
062: // DNS response codes
063: private static final int NO_ERROR = 0;
064: private static final int FORMAT_ERROR = 1;
065: private static final int SERVER_FAILURE = 2;
066: private static final int NAME_ERROR = 3;
067: private static final int NOT_IMPL = 4;
068: private static final int REFUSED = 5;
069:
070: private static final String[] rcodeDescription = { "No error",
071: "DNS format error", "DNS server failure",
072: "DNS name not found", "DNS operation not supported",
073: "DNS service refused" };
074:
075: private static final int DEFAULT_PORT = 53;
076: private InetAddress[] servers;
077: private int[] serverPorts;
078: private int timeout; // initial timeout on UDP queries in ms
079: private int retries; // number of UDP retries
080:
081: private DatagramSocket udpSocket;
082:
083: // Requests sent
084: private Set<Integer> reqs;
085:
086: // Responses received
087: private Map<Integer, byte[]> resps;
088:
089: //-------------------------------------------------------------------------
090:
091: /*
092: * Each server is of the form "server[:port]". IPv6 literal host names
093: * include delimiting brackets.
094: * "timeout" is the initial timeout interval (in ms) for UDP queries,
095: * and "retries" gives the number of retries per server.
096: */
097: public DnsClient(String[] servers, int timeout, int retries)
098: throws NamingException {
099: this .timeout = timeout;
100: this .retries = retries;
101: try {
102: udpSocket = new DatagramSocket();
103: } catch (java.net.SocketException e) {
104: NamingException ne = new ConfigurationException();
105: ne.setRootCause(e);
106: throw ne;
107: }
108:
109: this .servers = new InetAddress[servers.length];
110: serverPorts = new int[servers.length];
111:
112: for (int i = 0; i < servers.length; i++) {
113:
114: // Is optional port given?
115: int colon = servers[i].indexOf(':',
116: servers[i].indexOf(']') + 1);
117:
118: serverPorts[i] = (colon < 0) ? DEFAULT_PORT : Integer
119: .parseInt(servers[i].substring(colon + 1));
120: String server = (colon < 0) ? servers[i] : servers[i]
121: .substring(0, colon);
122: try {
123: this .servers[i] = InetAddress.getByName(server);
124: } catch (java.net.UnknownHostException e) {
125: NamingException ne = new ConfigurationException(
126: "Unknown DNS server: " + server);
127: ne.setRootCause(e);
128: throw ne;
129: }
130: }
131: reqs = Collections.synchronizedSet(new HashSet<Integer>());
132: resps = Collections
133: .synchronizedMap(new HashMap<Integer, byte[]>());
134: }
135:
136: protected void finalize() {
137: close();
138: }
139:
140: // A lock to access the request and response queues in tandem.
141: private Object queuesLock = new Object();
142:
143: public void close() {
144: udpSocket.close();
145: synchronized (queuesLock) {
146: reqs.clear();
147: resps.clear();
148: }
149: }
150:
151: private int ident = 0; // used to set the msg ID field
152: private Object identLock = new Object();
153:
154: /*
155: * If recursion is true, recursion is requested on the query.
156: * If auth is true, only authoritative responses are accepted; other
157: * responses throw NameNotFoundException.
158: */
159: ResourceRecords query(DnsName fqdn, int qclass, int qtype,
160: boolean recursion, boolean auth) throws NamingException {
161:
162: int xid;
163: synchronized (identLock) {
164: ident = 0xFFFF & (ident + 1);
165: xid = ident;
166: }
167:
168: // enqueue the outstanding request
169: reqs.add(xid);
170:
171: Packet pkt = makeQueryPacket(fqdn, xid, qclass, qtype,
172: recursion);
173:
174: Exception caughtException = null;
175: boolean[] doNotRetry = new boolean[servers.length];
176:
177: //
178: // The UDP retry strategy is to try the 1st server, and then
179: // each server in order. If no answer, double the timeout
180: // and try each server again.
181: //
182: for (int retry = 0; retry < retries; retry++) {
183:
184: // Try each name server.
185: for (int i = 0; i < servers.length; i++) {
186: if (doNotRetry[i]) {
187: continue;
188: }
189:
190: // send the request packet and wait for a response.
191: try {
192: if (debug) {
193: dprint("SEND ID (" + (retry + 1) + "): " + xid);
194: }
195:
196: byte[] msg = null;
197: msg = doUdpQuery(pkt, servers[i], serverPorts[i],
198: retry, xid);
199: //
200: // If the matching response is not got within the
201: // given timeout, check if the response was enqueued
202: // by some other thread, if not proceed with the next
203: // server or retry.
204: //
205: if (msg == null) {
206: if (resps.size() > 0) {
207: msg = lookupResponse(xid);
208: }
209: if (msg == null) { // try next server or retry
210: continue;
211: }
212: }
213: Header hdr = new Header(msg, msg.length);
214:
215: if (auth && !hdr.authoritative) {
216: caughtException = new NameNotFoundException(
217: "DNS response not authoritative");
218: doNotRetry[i] = true;
219: continue;
220: }
221: if (hdr.truncated) { // message is truncated -- try TCP
222:
223: // Try each server, starting with the one that just
224: // provided the truncated message.
225: for (int j = 0; j < servers.length; j++) {
226: int ij = (i + j) % servers.length;
227: if (doNotRetry[ij]) {
228: continue;
229: }
230: try {
231: Tcp tcp = new Tcp(servers[ij],
232: serverPorts[ij]);
233: byte[] msg2;
234: try {
235: msg2 = doTcpQuery(tcp, pkt);
236: } finally {
237: tcp.close();
238: }
239: Header hdr2 = new Header(msg2,
240: msg2.length);
241: if (hdr2.query) {
242: throw new CommunicationException(
243: "DNS error: expecting response");
244: }
245: checkResponseCode(hdr2);
246:
247: if (!auth || hdr2.authoritative) {
248: // Got a valid response
249: hdr = hdr2;
250: msg = msg2;
251: break;
252: } else {
253: doNotRetry[ij] = true;
254: }
255: } catch (Exception e) {
256: // Try next server, or use UDP response
257: }
258: } // servers
259: }
260: return new ResourceRecords(msg, msg.length, hdr,
261: false);
262:
263: } catch (IOException e) {
264: if (debug) {
265: dprint("Caught IOException:" + e);
266: }
267: if (caughtException == null) {
268: caughtException = e;
269: }
270: // Use reflection to allow pre-1.4 compilation.
271: // This won't be needed much longer.
272: if (e.getClass().getName().equals(
273: "java.net.PortUnreachableException")) {
274: doNotRetry[i] = true;
275: }
276: } catch (NameNotFoundException e) {
277: throw e;
278: } catch (CommunicationException e) {
279: if (caughtException == null) {
280: caughtException = e;
281: }
282: } catch (NamingException e) {
283: if (caughtException == null) {
284: caughtException = e;
285: }
286: doNotRetry[i] = true;
287: }
288: } // servers
289: } // retries
290:
291: reqs.remove(xid);
292: if (caughtException instanceof NamingException) {
293: throw (NamingException) caughtException;
294: }
295: // A network timeout or other error occurred.
296: NamingException ne = new CommunicationException("DNS error");
297: ne.setRootCause(caughtException);
298: throw ne;
299: }
300:
301: ResourceRecords queryZone(DnsName zone, int qclass,
302: boolean recursion) throws NamingException {
303:
304: int xid;
305: synchronized (identLock) {
306: ident = 0xFFFF & (ident + 1);
307: xid = ident;
308: }
309: Packet pkt = makeQueryPacket(zone, xid, qclass,
310: ResourceRecord.QTYPE_AXFR, recursion);
311: Exception caughtException = null;
312:
313: // Try each name server.
314: for (int i = 0; i < servers.length; i++) {
315: try {
316: Tcp tcp = new Tcp(servers[i], serverPorts[i]);
317: byte[] msg;
318: try {
319: msg = doTcpQuery(tcp, pkt);
320: Header hdr = new Header(msg, msg.length);
321: // Check only rcode as per
322: // draft-ietf-dnsext-axfr-clarify-04
323: checkResponseCode(hdr);
324: ResourceRecords rrs = new ResourceRecords(msg,
325: msg.length, hdr, true);
326: if (rrs.getFirstAnsType() != ResourceRecord.TYPE_SOA) {
327: throw new CommunicationException(
328: "DNS error: zone xfer doesn't begin with SOA");
329: }
330:
331: if (rrs.answer.size() == 1
332: || rrs.getLastAnsType() != ResourceRecord.TYPE_SOA) {
333: // The response is split into multiple DNS messages.
334: do {
335: msg = continueTcpQuery(tcp);
336: if (msg == null) {
337: throw new CommunicationException(
338: "DNS error: incomplete zone transfer");
339: }
340: hdr = new Header(msg, msg.length);
341: checkResponseCode(hdr);
342: rrs.add(msg, msg.length, hdr);
343: } while (rrs.getLastAnsType() != ResourceRecord.TYPE_SOA);
344: }
345:
346: // Delete the duplicate SOA record.
347: rrs.answer.removeElementAt(rrs.answer.size() - 1);
348: return rrs;
349:
350: } finally {
351: tcp.close();
352: }
353:
354: } catch (IOException e) {
355: caughtException = e;
356: } catch (NameNotFoundException e) {
357: throw e;
358: } catch (NamingException e) {
359: caughtException = e;
360: }
361: }
362: if (caughtException instanceof NamingException) {
363: throw (NamingException) caughtException;
364: }
365: NamingException ne = new CommunicationException(
366: "DNS error during zone transfer");
367: ne.setRootCause(caughtException);
368: throw ne;
369: }
370:
371: /**
372: * Tries to retreive an UDP packet matching the given xid
373: * received within the timeout.
374: * If a packet with different xid is received, the received packet
375: * is enqueued with the corresponding xid in 'resps'.
376: */
377: private byte[] doUdpQuery(Packet pkt, InetAddress server, int port,
378: int retry, int xid) throws IOException, NamingException {
379:
380: int minTimeout = 50; // msec after which there are no retries.
381:
382: synchronized (udpSocket) {
383: DatagramPacket opkt = new DatagramPacket(pkt.getData(), pkt
384: .length(), server, port);
385: DatagramPacket ipkt = new DatagramPacket(new byte[8000],
386: 8000);
387: udpSocket.connect(server, port);
388: int pktTimeout = (timeout * (1 << retry));
389: try {
390: udpSocket.send(opkt);
391:
392: // timeout remaining after successive 'receive()'
393: int timeoutLeft = pktTimeout;
394: int cnt = 0;
395: do {
396: if (debug) {
397: cnt++;
398: dprint("Trying RECEIVE(" + cnt + ") retry("
399: + (retry + 1) + ") for:" + xid
400: + " sock-timeout:" + timeoutLeft
401: + " ms.");
402: }
403: udpSocket.setSoTimeout(timeoutLeft);
404: long start = System.currentTimeMillis();
405: udpSocket.receive(ipkt);
406: long end = System.currentTimeMillis();
407:
408: byte[] data = new byte[ipkt.getLength()];
409: data = ipkt.getData();
410: if (isMatchResponse(data, xid)) {
411: return data;
412: }
413: timeoutLeft = pktTimeout - ((int) (end - start));
414: } while (timeoutLeft > minTimeout);
415:
416: } finally {
417: udpSocket.disconnect();
418: }
419: return null; // no matching packet received within the timeout
420: }
421: }
422:
423: /*
424: * Sends a TCP query, and returns the first DNS message in the response.
425: */
426: private byte[] doTcpQuery(Tcp tcp, Packet pkt) throws IOException {
427:
428: int len = pkt.length();
429: // Send 2-byte message length, then send message.
430: tcp.out.write(len >> 8);
431: tcp.out.write(len);
432: tcp.out.write(pkt.getData(), 0, len);
433: tcp.out.flush();
434:
435: byte[] msg = continueTcpQuery(tcp);
436: if (msg == null) {
437: throw new IOException("DNS error: no response");
438: }
439: return msg;
440: }
441:
442: /*
443: * Returns the next DNS message from the TCP socket, or null on EOF.
444: */
445: private byte[] continueTcpQuery(Tcp tcp) throws IOException {
446:
447: int lenHi = tcp.in.read(); // high-order byte of response length
448: if (lenHi == -1) {
449: return null; // EOF
450: }
451: int lenLo = tcp.in.read(); // low-order byte of response length
452: if (lenLo == -1) {
453: throw new IOException("Corrupted DNS response: bad length");
454: }
455: int len = (lenHi << 8) | lenLo;
456: byte[] msg = new byte[len];
457: int pos = 0; // next unfilled position in msg
458: while (len > 0) {
459: int n = tcp.in.read(msg, pos, len);
460: if (n == -1) {
461: throw new IOException(
462: "Corrupted DNS response: too little data");
463: }
464: len -= n;
465: pos += n;
466: }
467: return msg;
468: }
469:
470: private Packet makeQueryPacket(DnsName fqdn, int xid, int qclass,
471: int qtype, boolean recursion) {
472: int qnameLen = fqdn.getOctets();
473: int pktLen = DNS_HDR_SIZE + qnameLen + 4;
474: Packet pkt = new Packet(pktLen);
475:
476: short flags = recursion ? Header.RD_BIT : 0;
477:
478: pkt.putShort(xid, IDENT_OFFSET);
479: pkt.putShort(flags, FLAGS_OFFSET);
480: pkt.putShort(1, NUMQ_OFFSET);
481: pkt.putShort(0, NUMANS_OFFSET);
482: pkt.putInt(0, NUMAUTH_OFFSET);
483:
484: makeQueryName(fqdn, pkt, DNS_HDR_SIZE);
485: pkt.putShort(qtype, DNS_HDR_SIZE + qnameLen);
486: pkt.putShort(qclass, DNS_HDR_SIZE + qnameLen + 2);
487:
488: return pkt;
489: }
490:
491: // Builds a query name in pkt according to the RFC spec.
492: private void makeQueryName(DnsName fqdn, Packet pkt, int off) {
493:
494: // Loop through labels, least-significant first.
495: for (int i = fqdn.size() - 1; i >= 0; i--) {
496: String label = fqdn.get(i);
497: int len = label.length();
498:
499: pkt.putByte(len, off++);
500: for (int j = 0; j < len; j++) {
501: pkt.putByte(label.charAt(j), off++);
502: }
503: }
504: if (!fqdn.hasRootLabel()) {
505: pkt.putByte(0, off);
506: }
507: }
508:
509: //-------------------------------------------------------------------------
510:
511: private byte[] lookupResponse(Integer xid) throws NamingException {
512: //
513: // Check the queued responses: some other thread in between
514: // received the response for this request.
515: //
516: if (debug) {
517: dprint("LOOKUP for: " + xid + " Response Q:" + resps);
518: }
519: byte[] pkt;
520: if ((pkt = (byte[]) resps.get(xid)) != null) {
521: synchronized (queuesLock) {
522: resps.remove(xid);
523: reqs.remove(xid);
524: }
525: checkResponseCode(new Header(pkt, pkt.length));
526:
527: if (debug) {
528: dprint("FOUND (" + Thread.currentThread() + ") for:"
529: + xid);
530: }
531: }
532: return pkt;
533: }
534:
535: /*
536: * Checks the header of an incoming DNS response.
537: * Returns true if it matches the given xid and throws a naming
538: * exception, if appropriate, based on the response code.
539: */
540: private boolean isMatchResponse(byte[] pkt, int xid)
541: throws NamingException {
542:
543: Header hdr = new Header(pkt, pkt.length);
544: if (hdr.query) {
545: throw new CommunicationException(
546: "DNS error: expecting response");
547: }
548:
549: if (!reqs.contains(xid)) { // already received, ignore the response
550: return false;
551: }
552:
553: // common case- the request sent matches the subsequent response read
554: if (hdr.xid == xid) {
555: if (debug) {
556: dprint("XID MATCH:" + xid);
557: }
558:
559: // remove the response for the xid if received by some other thread.
560: synchronized (queuesLock) {
561: resps.remove(xid);
562: reqs.remove(xid);
563: }
564: checkResponseCode(hdr);
565: return true;
566: }
567:
568: //
569: // xid mis-match: enqueue the response, it may belong to some other
570: // thread that has not yet had a chance to read its response.
571: // enqueue only the first response, responses for retries are ignored.
572: //
573: synchronized (queuesLock) {
574: if (reqs.contains(xid)) { // enqueue only the first response
575: resps.put(xid, pkt);
576: }
577: }
578:
579: if (debug) {
580: dprint("NO-MATCH SEND ID:" + xid + " RECVD ID:" + hdr.xid
581: + " Response Q:" + resps + " Reqs size:"
582: + reqs.size());
583: }
584: return false;
585: }
586:
587: /*
588: * Throws an exception if appropriate for the response code of a
589: * given header.
590: */
591: private void checkResponseCode(Header hdr) throws NamingException {
592:
593: int rcode = hdr.rcode;
594: if (rcode == NO_ERROR) {
595: return;
596: }
597: String msg = (rcode < rcodeDescription.length) ? rcodeDescription[rcode]
598: : "DNS error";
599: msg += " [response code " + rcode + "]";
600:
601: switch (rcode) {
602: case SERVER_FAILURE:
603: throw new ServiceUnavailableException(msg);
604: case NAME_ERROR:
605: throw new NameNotFoundException(msg);
606: case NOT_IMPL:
607: case REFUSED:
608: throw new OperationNotSupportedException(msg);
609: case FORMAT_ERROR:
610: default:
611: throw new NamingException(msg);
612: }
613: }
614:
615: //-------------------------------------------------------------------------
616:
617: private static boolean debug = false;
618:
619: public static void setDebug(boolean flag) {
620: debug = flag;
621: }
622:
623: private static void dprint(String mess) {
624: if (debug) {
625: System.err.println("DNS: " + mess);
626: }
627: }
628:
629: }
630:
631: class Tcp {
632:
633: private Socket sock;
634: java.io.InputStream in;
635: java.io.OutputStream out;
636:
637: Tcp(InetAddress server, int port) throws IOException {
638: sock = new Socket(server, port);
639: sock.setTcpNoDelay(true);
640: out = new java.io.BufferedOutputStream(sock.getOutputStream());
641: in = new java.io.BufferedInputStream(sock.getInputStream());
642: }
643:
644: void close() throws IOException {
645: sock.close();
646: }
647: }
648:
649: /*
650: * javaos emulation -cj
651: */
652: class Packet {
653: byte buf[];
654:
655: Packet(int len) {
656: buf = new byte[len];
657: }
658:
659: Packet(byte data[], int len) {
660: buf = new byte[len];
661: System.arraycopy(data, 0, buf, 0, len);
662: }
663:
664: void putInt(int x, int off) {
665: buf[off + 0] = (byte) (x >> 24);
666: buf[off + 1] = (byte) (x >> 16);
667: buf[off + 2] = (byte) (x >> 8);
668: buf[off + 3] = (byte) x;
669: }
670:
671: void putShort(int x, int off) {
672: buf[off + 0] = (byte) (x >> 8);
673: buf[off + 1] = (byte) x;
674: }
675:
676: void putByte(int x, int off) {
677: buf[off] = (byte) x;
678: }
679:
680: void putBytes(byte src[], int src_offset, int dst_offset, int len) {
681: System.arraycopy(src, src_offset, buf, dst_offset, len);
682: }
683:
684: int length() {
685: return buf.length;
686: }
687:
688: byte[] getData() {
689: return buf;
690: }
691: }
|