Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/com/sun/jndi/dns/DnsClient.java
38924 views
/*1* Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.jndi.dns;2627import java.io.IOException;28import java.net.DatagramSocket;29import java.net.DatagramPacket;30import java.net.InetAddress;31import java.net.Socket;32import java.security.SecureRandom;33import javax.naming.*;3435import java.util.Collections;36import java.util.Map;37import java.util.HashMap;3839import sun.security.jca.JCAUtil;4041// Some of this code began life as part of sun.javaos.net.DnsClient42// originally by sritchie@eng 1/96. It was first hacked up for JNDI43// use by caveh@eng 6/97.444546/**47* The DnsClient class performs DNS client operations in support of DnsContext.48*49*/5051public class DnsClient {5253// DNS packet header field offsets54private static final int IDENT_OFFSET = 0;55private static final int FLAGS_OFFSET = 2;56private static final int NUMQ_OFFSET = 4;57private static final int NUMANS_OFFSET = 6;58private static final int NUMAUTH_OFFSET = 8;59private static final int NUMADD_OFFSET = 10;60private static final int DNS_HDR_SIZE = 12;6162// DNS response codes63private static final int NO_ERROR = 0;64private static final int FORMAT_ERROR = 1;65private static final int SERVER_FAILURE = 2;66private static final int NAME_ERROR = 3;67private static final int NOT_IMPL = 4;68private static final int REFUSED = 5;6970private 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};7879private static final int DEFAULT_PORT = 53;80private static final int TRANSACTION_ID_BOUND = 0x10000;81private static final SecureRandom random = JCAUtil.getSecureRandom();82private InetAddress[] servers;83private int[] serverPorts;84private int timeout; // initial timeout on UDP queries in ms85private int retries; // number of UDP retries8687private final Object udpSocketLock = new Object();88private static final DNSDatagramSocketFactory factory =89new DNSDatagramSocketFactory(random);9091// Requests sent92private Map<Integer, ResourceRecord> reqs;9394// Responses received95private Map<Integer, byte[]> resps;9697//-------------------------------------------------------------------------9899/*100* Each server is of the form "server[:port]". IPv6 literal host names101* include delimiting brackets.102* "timeout" is the initial timeout interval (in ms) for UDP queries,103* and "retries" gives the number of retries per server.104*/105public DnsClient(String[] servers, int timeout, int retries)106throws NamingException {107this.timeout = timeout;108this.retries = retries;109this.servers = new InetAddress[servers.length];110serverPorts = new int[servers.length];111112for (int i = 0; i < servers.length; i++) {113114// Is optional port given?115int colon = servers[i].indexOf(':',116servers[i].indexOf(']') + 1);117118serverPorts[i] = (colon < 0)119? DEFAULT_PORT120: Integer.parseInt(servers[i].substring(colon + 1));121String server = (colon < 0)122? servers[i]123: servers[i].substring(0, colon);124try {125this.servers[i] = InetAddress.getByName(server);126} catch (java.net.UnknownHostException e) {127NamingException ne = new ConfigurationException(128"Unknown DNS server: " + server);129ne.setRootCause(e);130throw ne;131}132}133reqs = Collections.synchronizedMap(134new HashMap<Integer, ResourceRecord>());135resps = Collections.synchronizedMap(new HashMap<Integer, byte[]>());136}137138DatagramSocket getDatagramSocket() throws NamingException {139try {140return factory.open();141} catch (java.net.SocketException e) {142NamingException ne = new ConfigurationException();143ne.setRootCause(e);144throw ne;145}146}147148protected void finalize() {149close();150}151152// A lock to access the request and response queues in tandem.153private Object queuesLock = new Object();154155public void close() {156synchronized (queuesLock) {157reqs.clear();158resps.clear();159}160}161162/*163* If recursion is true, recursion is requested on the query.164* If auth is true, only authoritative responses are accepted; other165* responses throw NameNotFoundException.166*/167ResourceRecords query(DnsName fqdn, int qclass, int qtype,168boolean recursion, boolean auth)169throws NamingException {170171int xid;172Packet pkt;173ResourceRecord collision;174175do {176// Generate a random transaction ID177xid = random.nextInt(TRANSACTION_ID_BOUND);178pkt = makeQueryPacket(fqdn, xid, qclass, qtype, recursion);179180// enqueue the outstanding request181collision = reqs.putIfAbsent(xid, new ResourceRecord(pkt.getData(),182pkt.length(), Header.HEADER_SIZE, true, false));183184} while (collision != null);185186Exception caughtException = null;187boolean[] doNotRetry = new boolean[servers.length];188189try {190//191// The UDP retry strategy is to try the 1st server, and then192// each server in order. If no answer, double the timeout193// and try each server again.194//195for (int retry = 0; retry < retries; retry++) {196197// Try each name server.198for (int i = 0; i < servers.length; i++) {199if (doNotRetry[i]) {200continue;201}202203// send the request packet and wait for a response.204try {205if (debug) {206dprint("SEND ID (" + (retry + 1) + "): " + xid);207}208209byte[] msg = null;210msg = doUdpQuery(pkt, servers[i], serverPorts[i],211retry, xid);212//213// If the matching response is not got within the214// given timeout, check if the response was enqueued215// by some other thread, if not proceed with the next216// server or retry.217//218if (msg == null) {219if (resps.size() > 0) {220msg = lookupResponse(xid);221}222if (msg == null) { // try next server or retry223continue;224}225}226Header hdr = new Header(msg, msg.length);227228if (auth && !hdr.authoritative) {229caughtException = new NameNotFoundException(230"DNS response not authoritative");231doNotRetry[i] = true;232continue;233}234if (hdr.truncated) { // message is truncated -- try TCP235236// Try each server, starting with the one that just237// provided the truncated message.238for (int j = 0; j < servers.length; j++) {239int ij = (i + j) % servers.length;240if (doNotRetry[ij]) {241continue;242}243try {244Tcp tcp =245new Tcp(servers[ij], serverPorts[ij]);246byte[] msg2;247try {248msg2 = doTcpQuery(tcp, pkt);249} finally {250tcp.close();251}252Header hdr2 = new Header(msg2, msg2.length);253if (hdr2.query) {254throw new CommunicationException(255"DNS error: expecting response");256}257checkResponseCode(hdr2);258259if (!auth || hdr2.authoritative) {260// Got a valid response261hdr = hdr2;262msg = msg2;263break;264} else {265doNotRetry[ij] = true;266}267} catch (Exception e) {268// Try next server, or use UDP response269}270} // servers271}272return new ResourceRecords(msg, msg.length, hdr, false);273274} catch (IOException e) {275if (debug) {276dprint("Caught IOException:" + e);277}278if (caughtException == null) {279caughtException = e;280}281// Use reflection to allow pre-1.4 compilation.282// This won't be needed much longer.283if (e.getClass().getName().equals(284"java.net.PortUnreachableException")) {285doNotRetry[i] = true;286}287} catch (NameNotFoundException e) {288// This is authoritative, so return immediately289throw e;290} catch (CommunicationException e) {291if (caughtException == null) {292caughtException = e;293}294} catch (NamingException e) {295if (caughtException == null) {296caughtException = e;297}298doNotRetry[i] = true;299}300} // servers301} // retries302303} finally {304reqs.remove(xid); // cleanup305}306307if (caughtException instanceof NamingException) {308throw (NamingException) caughtException;309}310// A network timeout or other error occurred.311NamingException ne = new CommunicationException("DNS error");312ne.setRootCause(caughtException);313throw ne;314}315316ResourceRecords queryZone(DnsName zone, int qclass, boolean recursion)317throws NamingException {318319int xid = random.nextInt(TRANSACTION_ID_BOUND);320321Packet pkt = makeQueryPacket(zone, xid, qclass,322ResourceRecord.QTYPE_AXFR, recursion);323Exception caughtException = null;324325// Try each name server.326for (int i = 0; i < servers.length; i++) {327try {328Tcp tcp = new Tcp(servers[i], serverPorts[i]);329byte[] msg;330try {331msg = doTcpQuery(tcp, pkt);332Header hdr = new Header(msg, msg.length);333// Check only rcode as per334// draft-ietf-dnsext-axfr-clarify-04335checkResponseCode(hdr);336ResourceRecords rrs =337new ResourceRecords(msg, msg.length, hdr, true);338if (rrs.getFirstAnsType() != ResourceRecord.TYPE_SOA) {339throw new CommunicationException(340"DNS error: zone xfer doesn't begin with SOA");341}342343if (rrs.answer.size() == 1 ||344rrs.getLastAnsType() != ResourceRecord.TYPE_SOA) {345// The response is split into multiple DNS messages.346do {347msg = continueTcpQuery(tcp);348if (msg == null) {349throw new CommunicationException(350"DNS error: incomplete zone transfer");351}352hdr = new Header(msg, msg.length);353checkResponseCode(hdr);354rrs.add(msg, msg.length, hdr);355} while (rrs.getLastAnsType() !=356ResourceRecord.TYPE_SOA);357}358359// Delete the duplicate SOA record.360rrs.answer.removeElementAt(rrs.answer.size() - 1);361return rrs;362363} finally {364tcp.close();365}366367} catch (IOException e) {368caughtException = e;369} catch (NameNotFoundException e) {370throw e;371} catch (NamingException e) {372caughtException = e;373}374}375if (caughtException instanceof NamingException) {376throw (NamingException) caughtException;377}378NamingException ne = new CommunicationException(379"DNS error during zone transfer");380ne.setRootCause(caughtException);381throw ne;382}383384385/**386* Tries to retreive an UDP packet matching the given xid387* received within the timeout.388* If a packet with different xid is received, the received packet389* is enqueued with the corresponding xid in 'resps'.390*/391private byte[] doUdpQuery(Packet pkt, InetAddress server,392int port, int retry, int xid)393throws IOException, NamingException {394395int minTimeout = 50; // msec after which there are no retries.396397synchronized (udpSocketLock) {398try (DatagramSocket udpSocket = getDatagramSocket()) {399DatagramPacket opkt = new DatagramPacket(400pkt.getData(), pkt.length(), server, port);401DatagramPacket ipkt = new DatagramPacket(new byte[8000], 8000);402// Packets may only be sent to or received from this server address403udpSocket.connect(server, port);404int pktTimeout = (timeout * (1 << retry));405try {406udpSocket.send(opkt);407408// timeout remaining after successive 'receive()'409int timeoutLeft = pktTimeout;410int cnt = 0;411do {412if (debug) {413cnt++;414dprint("Trying RECEIVE(" +415cnt + ") retry(" + (retry + 1) +416") for:" + xid + " sock-timeout:" +417timeoutLeft + " ms.");418}419udpSocket.setSoTimeout(timeoutLeft);420long start = System.currentTimeMillis();421udpSocket.receive(ipkt);422long end = System.currentTimeMillis();423424byte[] data = ipkt.getData();425if (isMatchResponse(data, xid)) {426return data;427}428timeoutLeft = pktTimeout - ((int) (end - start));429} while (timeoutLeft > minTimeout);430431} finally {432udpSocket.disconnect();433}434return null; // no matching packet received within the timeout435}436}437}438439/*440* Sends a TCP query, and returns the first DNS message in the response.441*/442private byte[] doTcpQuery(Tcp tcp, Packet pkt) throws IOException {443444int len = pkt.length();445// Send 2-byte message length, then send message.446tcp.out.write(len >> 8);447tcp.out.write(len);448tcp.out.write(pkt.getData(), 0, len);449tcp.out.flush();450451byte[] msg = continueTcpQuery(tcp);452if (msg == null) {453throw new IOException("DNS error: no response");454}455return msg;456}457458/*459* Returns the next DNS message from the TCP socket, or null on EOF.460*/461private byte[] continueTcpQuery(Tcp tcp) throws IOException {462463int lenHi = tcp.in.read(); // high-order byte of response length464if (lenHi == -1) {465return null; // EOF466}467int lenLo = tcp.in.read(); // low-order byte of response length468if (lenLo == -1) {469throw new IOException("Corrupted DNS response: bad length");470}471int len = (lenHi << 8) | lenLo;472byte[] msg = new byte[len];473int pos = 0; // next unfilled position in msg474while (len > 0) {475int n = tcp.in.read(msg, pos, len);476if (n == -1) {477throw new IOException(478"Corrupted DNS response: too little data");479}480len -= n;481pos += n;482}483return msg;484}485486private Packet makeQueryPacket(DnsName fqdn, int xid,487int qclass, int qtype, boolean recursion) {488int qnameLen = fqdn.getOctets();489int pktLen = DNS_HDR_SIZE + qnameLen + 4;490Packet pkt = new Packet(pktLen);491492short flags = recursion ? Header.RD_BIT : 0;493494pkt.putShort(xid, IDENT_OFFSET);495pkt.putShort(flags, FLAGS_OFFSET);496pkt.putShort(1, NUMQ_OFFSET);497pkt.putShort(0, NUMANS_OFFSET);498pkt.putInt(0, NUMAUTH_OFFSET);499500makeQueryName(fqdn, pkt, DNS_HDR_SIZE);501pkt.putShort(qtype, DNS_HDR_SIZE + qnameLen);502pkt.putShort(qclass, DNS_HDR_SIZE + qnameLen + 2);503504return pkt;505}506507// Builds a query name in pkt according to the RFC spec.508private void makeQueryName(DnsName fqdn, Packet pkt, int off) {509510// Loop through labels, least-significant first.511for (int i = fqdn.size() - 1; i >= 0; i--) {512String label = fqdn.get(i);513int len = label.length();514515pkt.putByte(len, off++);516for (int j = 0; j < len; j++) {517pkt.putByte(label.charAt(j), off++);518}519}520if (!fqdn.hasRootLabel()) {521pkt.putByte(0, off);522}523}524525//-------------------------------------------------------------------------526527private byte[] lookupResponse(Integer xid) throws NamingException {528//529// Check the queued responses: some other thread in between530// received the response for this request.531//532if (debug) {533dprint("LOOKUP for: " + xid +534"\tResponse Q:" + resps);535}536byte[] pkt;537if ((pkt = resps.get(xid)) != null) {538checkResponseCode(new Header(pkt, pkt.length));539synchronized (queuesLock) {540resps.remove(xid);541reqs.remove(xid);542}543544if (debug) {545dprint("FOUND (" + Thread.currentThread() +546") for:" + xid);547}548}549return pkt;550}551552/*553* Checks the header of an incoming DNS response.554* Returns true if it matches the given xid and throws a naming555* exception, if appropriate, based on the response code.556*557* Also checks that the domain name, type and class in the response558* match those in the original query.559*/560private boolean isMatchResponse(byte[] pkt, int xid)561throws NamingException {562563Header hdr = new Header(pkt, pkt.length);564if (hdr.query) {565throw new CommunicationException("DNS error: expecting response");566}567568if (!reqs.containsKey(xid)) { // already received, ignore the response569return false;570}571572// common case- the request sent matches the subsequent response read573if (hdr.xid == xid) {574if (debug) {575dprint("XID MATCH:" + xid);576}577checkResponseCode(hdr);578if (!hdr.query && hdr.numQuestions == 1) {579580ResourceRecord rr = new ResourceRecord(pkt, pkt.length,581Header.HEADER_SIZE, true, false);582583// Retrieve the original query584ResourceRecord query = reqs.get(xid);585int qtype = query.getType();586int qclass = query.getRrclass();587DnsName qname = query.getName();588589// Check that the type/class/name in the query section of the590// response match those in the original query591if ((qtype == ResourceRecord.QTYPE_STAR ||592qtype == rr.getType()) &&593(qclass == ResourceRecord.QCLASS_STAR ||594qclass == rr.getRrclass()) &&595qname.equals(rr.getName())) {596597if (debug) {598dprint("MATCH NAME:" + qname + " QTYPE:" + qtype +599" QCLASS:" + qclass);600}601602// Remove the response for the xid if received by some other603// thread.604synchronized (queuesLock) {605resps.remove(xid);606reqs.remove(xid);607}608return true;609610} else {611if (debug) {612dprint("NO-MATCH NAME:" + qname + " QTYPE:" + qtype +613" QCLASS:" + qclass);614}615}616}617return false;618}619620//621// xid mis-match: enqueue the response, it may belong to some other622// thread that has not yet had a chance to read its response.623// enqueue only the first response, responses for retries are ignored.624//625synchronized (queuesLock) {626if (reqs.containsKey(hdr.xid)) { // enqueue only the first response627resps.put(hdr.xid, pkt);628}629}630631if (debug) {632dprint("NO-MATCH SEND ID:" +633xid + " RECVD ID:" + hdr.xid +634" Response Q:" + resps +635" Reqs size:" + reqs.size());636}637return false;638}639640/*641* Throws an exception if appropriate for the response code of a642* given header.643*/644private void checkResponseCode(Header hdr) throws NamingException {645646int rcode = hdr.rcode;647if (rcode == NO_ERROR) {648return;649}650String msg = (rcode < rcodeDescription.length)651? rcodeDescription[rcode]652: "DNS error";653msg += " [response code " + rcode + "]";654655switch (rcode) {656case SERVER_FAILURE:657throw new ServiceUnavailableException(msg);658case NAME_ERROR:659throw new NameNotFoundException(msg);660case NOT_IMPL:661case REFUSED:662throw new OperationNotSupportedException(msg);663case FORMAT_ERROR:664default:665throw new NamingException(msg);666}667}668669//-------------------------------------------------------------------------670671private static final boolean debug = false;672673private static void dprint(String mess) {674if (debug) {675System.err.println("DNS: " + mess);676}677}678679}680681class Tcp {682683private Socket sock;684java.io.InputStream in;685java.io.OutputStream out;686687Tcp(InetAddress server, int port) throws IOException {688sock = new Socket(server, port);689sock.setTcpNoDelay(true);690out = new java.io.BufferedOutputStream(sock.getOutputStream());691in = new java.io.BufferedInputStream(sock.getInputStream());692}693694void close() throws IOException {695sock.close();696}697}698699/*700* javaos emulation -cj701*/702class Packet {703byte buf[];704705Packet(int len) {706buf = new byte[len];707}708709Packet(byte data[], int len) {710buf = new byte[len];711System.arraycopy(data, 0, buf, 0, len);712}713714void putInt(int x, int off) {715buf[off + 0] = (byte)(x >> 24);716buf[off + 1] = (byte)(x >> 16);717buf[off + 2] = (byte)(x >> 8);718buf[off + 3] = (byte)x;719}720721void putShort(int x, int off) {722buf[off + 0] = (byte)(x >> 8);723buf[off + 1] = (byte)x;724}725726void putByte(int x, int off) {727buf[off] = (byte)x;728}729730void putBytes(byte src[], int src_offset, int dst_offset, int len) {731System.arraycopy(src, src_offset, buf, dst_offset, len);732}733734int length() {735return buf.length;736}737738byte[] getData() {739return buf;740}741}742743744