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