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