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