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