1 /* 2 * Copyright (c) 2000, 2014, 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 = ipkt.getData(); 415 if (isMatchResponse(data, xid)) { 416 return data; 417 } 418 timeoutLeft = pktTimeout - ((int) (end - start)); 419 } while (timeoutLeft > minTimeout); 420 421 } finally { 422 udpSocket.disconnect(); 423 } 424 return null; // no matching packet received within the timeout 425 } 426 } 427 428 /* 429 * Sends a TCP query, and returns the first DNS message in the response. 430 */ 431 private byte[] doTcpQuery(Tcp tcp, Packet pkt) throws IOException { 432 433 int len = pkt.length(); 434 // Send 2-byte message length, then send message. 435 tcp.out.write(len >> 8); 436 tcp.out.write(len); 437 tcp.out.write(pkt.getData(), 0, len); 438 tcp.out.flush(); 439 440 byte[] msg = continueTcpQuery(tcp); 441 if (msg == null) { 442 throw new IOException("DNS error: no response"); 443 } 444 return msg; 445 } 446 447 /* 448 * Returns the next DNS message from the TCP socket, or null on EOF. 449 */ 450 private byte[] continueTcpQuery(Tcp tcp) throws IOException { 451 452 int lenHi = tcp.in.read(); // high-order byte of response length 453 if (lenHi == -1) { 454 return null; // EOF 455 } 456 int lenLo = tcp.in.read(); // low-order byte of response length 457 if (lenLo == -1) { 458 throw new IOException("Corrupted DNS response: bad length"); 459 } 460 int len = (lenHi << 8) | lenLo; 461 byte[] msg = new byte[len]; 462 int pos = 0; // next unfilled position in msg 463 while (len > 0) { 464 int n = tcp.in.read(msg, pos, len); 465 if (n == -1) { 466 throw new IOException( 467 "Corrupted DNS response: too little data"); 468 } 469 len -= n; 470 pos += n; 471 } 472 return msg; 473 } 474 475 private Packet makeQueryPacket(DnsName fqdn, int xid, 476 int qclass, int qtype, boolean recursion) { 477 int qnameLen = fqdn.getOctets(); 478 int pktLen = DNS_HDR_SIZE + qnameLen + 4; 479 Packet pkt = new Packet(pktLen); 480 481 short flags = recursion ? Header.RD_BIT : 0; 482 483 pkt.putShort(xid, IDENT_OFFSET); 484 pkt.putShort(flags, FLAGS_OFFSET); 485 pkt.putShort(1, NUMQ_OFFSET); 486 pkt.putShort(0, NUMANS_OFFSET); 487 pkt.putInt(0, NUMAUTH_OFFSET); 488 489 makeQueryName(fqdn, pkt, DNS_HDR_SIZE); 490 pkt.putShort(qtype, DNS_HDR_SIZE + qnameLen); 491 pkt.putShort(qclass, DNS_HDR_SIZE + qnameLen + 2); 492 493 return pkt; 494 } 495 496 // Builds a query name in pkt according to the RFC spec. 497 private void makeQueryName(DnsName fqdn, Packet pkt, int off) { 498 499 // Loop through labels, least-significant first. 500 for (int i = fqdn.size() - 1; i >= 0; i--) { 501 String label = fqdn.get(i); 502 int len = label.length(); 503 504 pkt.putByte(len, off++); 505 for (int j = 0; j < len; j++) { 506 pkt.putByte(label.charAt(j), off++); 507 } 508 } 509 if (!fqdn.hasRootLabel()) { 510 pkt.putByte(0, off); 511 } 512 } 513 514 //------------------------------------------------------------------------- 515 516 private byte[] lookupResponse(Integer xid) throws NamingException { 517 // 518 // Check the queued responses: some other thread in between 519 // received the response for this request. 520 // 521 if (debug) { 522 dprint("LOOKUP for: " + xid + 523 "\tResponse Q:" + resps); 524 } 525 byte[] pkt; 526 if ((pkt = resps.get(xid)) != null) { 527 checkResponseCode(new Header(pkt, pkt.length)); 528 synchronized (queuesLock) { 529 resps.remove(xid); 530 reqs.remove(xid); 531 } 532 533 if (debug) { 534 dprint("FOUND (" + Thread.currentThread() + 535 ") for:" + xid); 536 } 537 } 538 return pkt; 539 } 540 541 /* 542 * Checks the header of an incoming DNS response. 543 * Returns true if it matches the given xid and throws a naming 544 * exception, if appropriate, based on the response code. 545 */ 546 private boolean isMatchResponse(byte[] pkt, int xid) 547 throws NamingException { 548 549 Header hdr = new Header(pkt, pkt.length); 550 if (hdr.query) { 551 throw new CommunicationException("DNS error: expecting response"); 552 } 553 554 if (!reqs.contains(xid)) { // already received, ignore the response 555 return false; 556 } 557 558 // common case- the request sent matches the subsequent response read 559 if (hdr.xid == xid) { 560 if (debug) { 561 dprint("XID MATCH:" + xid); 562 } 563 564 checkResponseCode(hdr); 565 // remove the response for the xid if received by some other thread. 566 synchronized (queuesLock) { 567 resps.remove(xid); 568 reqs.remove(xid); 569 } 570 return true; 571 } 572 573 // 574 // xid mis-match: enqueue the response, it may belong to some other 575 // thread that has not yet had a chance to read its response. 576 // enqueue only the first response, responses for retries are ignored. 577 // 578 synchronized (queuesLock) { 579 if (reqs.contains(hdr.xid)) { // enqueue only the first response 580 resps.put(hdr.xid, pkt); 581 } 582 } 583 584 if (debug) { 585 dprint("NO-MATCH SEND ID:" + 586 xid + " RECVD ID:" + hdr.xid + 587 " Response Q:" + resps + 588 " Reqs size:" + reqs.size()); 589 } 590 return false; 591 } 592 593 /* 594 * Throws an exception if appropriate for the response code of a 595 * given header. 596 */ 597 private void checkResponseCode(Header hdr) throws NamingException { 598 599 int rcode = hdr.rcode; 600 if (rcode == NO_ERROR) { 601 return; 602 } 603 String msg = (rcode < rcodeDescription.length) 604 ? rcodeDescription[rcode] 605 : "DNS error"; 606 msg += " [response code " + rcode + "]"; 607 608 switch (rcode) { 609 case SERVER_FAILURE: 610 throw new ServiceUnavailableException(msg); 611 case NAME_ERROR: 612 throw new NameNotFoundException(msg); 613 case NOT_IMPL: 614 case REFUSED: 615 throw new OperationNotSupportedException(msg); 616 case FORMAT_ERROR: 617 default: 618 throw new NamingException(msg); 619 } 620 } 621 622 //------------------------------------------------------------------------- 623 624 private static final boolean debug = false; 625 626 private static void dprint(String mess) { 627 if (debug) { 628 System.err.println("DNS: " + mess); 629 } 630 } 631 632 } 633 634 class Tcp { 635 636 private Socket sock; 637 java.io.InputStream in; 638 java.io.OutputStream out; 639 640 Tcp(InetAddress server, int port) throws IOException { 641 sock = new Socket(server, port); 642 sock.setTcpNoDelay(true); 643 out = new java.io.BufferedOutputStream(sock.getOutputStream()); 644 in = new java.io.BufferedInputStream(sock.getInputStream()); 645 } 646 647 void close() throws IOException { 648 sock.close(); 649 } 650 } 651 652 /* 653 * javaos emulation -cj 654 */ 655 class Packet { 656 byte buf[]; 657 658 Packet(int len) { 659 buf = new byte[len]; 660 } 661 662 Packet(byte data[], int len) { 663 buf = new byte[len]; 664 System.arraycopy(data, 0, buf, 0, len); 665 } 666 667 void putInt(int x, int off) { 668 buf[off + 0] = (byte)(x >> 24); 669 buf[off + 1] = (byte)(x >> 16); 670 buf[off + 2] = (byte)(x >> 8); 671 buf[off + 3] = (byte)x; 672 } 673 674 void putShort(int x, int off) { 675 buf[off + 0] = (byte)(x >> 8); 676 buf[off + 1] = (byte)x; 677 } 678 679 void putByte(int x, int off) { 680 buf[off] = (byte)x; 681 } 682 683 void putBytes(byte src[], int src_offset, int dst_offset, int len) { 684 System.arraycopy(src, src_offset, buf, dst_offset, len); 685 } 686 687 int length() { 688 return buf.length; 689 } 690 691 byte[] getData() { 692 return buf; 693 } 694 }