8195976: Add JNDI test com/sun/jndi/dns/AttributeTests/GetAny.java
authorxyin
Tue, 13 Feb 2018 12:19:37 +0800
changeset 48851 1d8f882f2b2f
parent 48850 4b9835e3215b
child 48852 478e198da84b
8195976: Add JNDI test com/sun/jndi/dns/AttributeTests/GetAny.java Reviewed-by: vtewari, rriggs, alanb, jjiang, xiaofeya
test/jdk/com/sun/jndi/dns/AttributeTests/GetAny.dns
test/jdk/com/sun/jndi/dns/AttributeTests/GetAny.java
test/jdk/com/sun/jndi/dns/lib/DNSServer.java
test/jdk/com/sun/jndi/dns/lib/DNSTestUtils.java
test/jdk/com/sun/jndi/dns/lib/DNSTracer.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/dns/AttributeTests/GetAny.dns	Tue Feb 13 12:19:37 2018 +0800
@@ -0,0 +1,110 @@
+#
+# Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+################################################################################
+# Capture file for GetAny.java
+#
+# NOTE: This hexadecimal dump of DNS protocol messages was generated by
+#       running the GetAny application program against a real DNS
+#       server along with DNSTracer
+#
+################################################################################
+
+# DNS Request
+
+0000: 32 72 01 00 00 01 00 00   00 00 00 00 05 68 6F 73  2r...........hos
+0010: 74 31 07 64 6F 6D 61 69   6E 31 03 63 6F 6D 00 00  t1.domain1.com..
+0020: FF 00 01                                           ...
+
+
+# DNS Response
+
+0000: 32 72 85 80 00 01 00 06   00 01 00 01 05 68 6F 73  2r...........hos
+0010: 74 31 07 64 6F 6D 61 69   6E 31 03 63 6F 6D 00 00  t1.domain1.com..
+0020: FF 00 01 C0 0C 00 10 00   01 00 00 8C A0 00 15 14  ................
+0030: 41 20 76 65 72 79 20 70   6F 70 75 6C 61 72 20 68  A very popular h
+0040: 6F 73 74 2E C0 0C 00 1D   00 01 00 00 8C A0 00 10  ost.............
+0050: 00 12 16 13 88 97 1A 35   69 68 41 38 00 9B 16 58  .......5ihA8...X
+0060: C0 0C 00 0D 00 01 00 00   8C A0 00 13 0C 54 68 65  .............The
+0070: 20 4F 72 69 67 69 6E 61   6C 05 53 75 6E 6E 79 C0   Original.Sunny.
+0080: 0C 00 0F 00 01 00 00 8C   A0 00 11 00 0A 05 72 65  ..............re
+0090: 6C 61 79 04 6F 68 69 6F   02 75 73 00 C0 0C 00 0F  lay.ohio.us.....
+00A0: 00 01 00 00 8C A0 00 10   00 14 05 72 65 6C 61 79  ...........relay
+00B0: 05 74 65 78 61 73 C0 98   C0 0C 00 01 00 01 00 01  .texas..........
+00C0: 51 80 00 04 01 02 04 01   C0 12 00 02 00 01 00 00  Q...............
+00D0: 8C A0 00 05 02 6E 73 C0   12 C0 D4 00 01 00 01 00  .....ns.........
+00E0: 00 8C A0 00 04 7F 00 00   01                       .........
+
+
+# DNS Request
+
+0000: 41 80 01 00 00 01 00 00   00 00 00 00 05 68 6F 73  A............hos
+0010: 74 31 07 64 6F 6D 61 69   6E 31 03 63 6F 6D 00 00  t1.domain1.com..
+0020: FF 00 FF                                           ...
+
+
+# DNS Response
+
+0000: 41 80 85 80 00 01 00 06   00 01 00 01 05 68 6F 73  A............hos
+0010: 74 31 07 64 6F 6D 61 69   6E 31 03 63 6F 6D 00 00  t1.domain1.com..
+0020: FF 00 FF C0 0C 00 10 00   01 00 00 8C A0 00 15 14  ................
+0030: 41 20 76 65 72 79 20 70   6F 70 75 6C 61 72 20 68  A very popular h
+0040: 6F 73 74 2E C0 0C 00 1D   00 01 00 00 8C A0 00 10  ost.............
+0050: 00 12 16 13 88 97 1A 35   69 68 41 38 00 9B 16 58  .......5ihA8...X
+0060: C0 0C 00 0D 00 01 00 00   8C A0 00 13 0C 54 68 65  .............The
+0070: 20 4F 72 69 67 69 6E 61   6C 05 53 75 6E 6E 79 C0   Original.Sunny.
+0080: 0C 00 0F 00 01 00 00 8C   A0 00 12 00 14 05 72 65  ..............re
+0090: 6C 61 79 05 74 65 78 61   73 02 75 73 00 C0 0C 00  lay.texas.us....
+00A0: 0F 00 01 00 00 8C A0 00   0F 00 0A 05 72 65 6C 61  ............rela
+00B0: 79 04 6F 68 69 6F C0 99   C0 0C 00 01 00 01 00 01  y.ohio..........
+00C0: 51 80 00 04 01 02 04 01   C0 12 00 02 00 01 00 00  Q...............
+00D0: 8C A0 00 05 02 6E 73 C0   12 C0 D4 00 01 00 01 00  .....ns.........
+00E0: 00 8C A0 00 04 7F 00 00   01                       .........
+
+
+# DNS Request
+
+0000: 65 2E 01 00 00 01 00 00   00 00 00 00 05 68 6F 73  e............hos
+0010: 74 31 07 64 6F 6D 61 69   6E 31 03 63 6F 6D 00 00  t1.domain1.com..
+0020: FF 00 01                                           ...
+
+
+# DNS Response
+
+0000: 65 2E 85 80 00 01 00 06   00 01 00 01 05 68 6F 73  e............hos
+0010: 74 31 07 64 6F 6D 61 69   6E 31 03 63 6F 6D 00 00  t1.domain1.com..
+0020: FF 00 01 C0 0C 00 10 00   01 00 00 8C A0 00 15 14  ................
+0030: 41 20 76 65 72 79 20 70   6F 70 75 6C 61 72 20 68  A very popular h
+0040: 6F 73 74 2E C0 0C 00 1D   00 01 00 00 8C A0 00 10  ost.............
+0050: 00 12 16 13 88 97 1A 35   69 68 41 38 00 9B 16 58  .......5ihA8...X
+0060: C0 0C 00 0D 00 01 00 00   8C A0 00 13 0C 54 68 65  .............The
+0070: 20 4F 72 69 67 69 6E 61   6C 05 53 75 6E 6E 79 C0   Original.Sunny.
+0080: 0C 00 0F 00 01 00 00 8C   A0 00 11 00 0A 05 72 65  ..............re
+0090: 6C 61 79 04 6F 68 69 6F   02 75 73 00 C0 0C 00 0F  lay.ohio.us.....
+00A0: 00 01 00 00 8C A0 00 10   00 14 05 72 65 6C 61 79  ...........relay
+00B0: 05 74 65 78 61 73 C0 98   C0 0C 00 01 00 01 00 01  .texas..........
+00C0: 51 80 00 04 01 02 04 01   C0 12 00 02 00 01 00 00  Q...............
+00D0: 8C A0 00 05 02 6E 73 C0   12 C0 D4 00 01 00 01 00  .....ns.........
+00E0: 00 8C A0 00 04 7F 00 00   01                       .........
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/dns/AttributeTests/GetAny.java	Tue Feb 13 12:19:37 2018 +0800
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8195976
+ * @summary Tests that we can get the attributes of a DNS entry using special
+ *          qualifiers.
+ * @modules java.xml.bind
+ *          java.base/sun.security.util
+ * @library ../lib/
+ * @build DNSTestUtils DNSServer DNSTracer
+ * @run main GetAny
+ */
+
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.util.Hashtable;
+
+public class GetAny {
+    private static final String KEY = "host1";
+
+    private static final String[] MANDATORY = { "A", "MX", "HINFO", "TXT", "29"
+            // "LOC"
+    };
+
+    private static final String[] OPTIONAL = {};
+
+    public static void main(String argv[]) throws Exception {
+        // Create socket on localhost only to avoid possible noise packet
+        DatagramSocket socket = new DatagramSocket(0,
+                InetAddress.getLoopbackAddress());
+
+        // initialize test
+        Hashtable<Object, Object> env;
+
+        env = DNSTestUtils.initEnv(socket, GetAny.class.getName(), argv);
+
+        DirContext ctx = null;
+
+        try {
+            // connect to server
+            ctx = new InitialDirContext(env);
+
+            // Any type from IN class
+            Attributes retAttrs = ctx.getAttributes(KEY, new String[] { "*" });
+            DNSTestUtils.verifySchema(retAttrs, MANDATORY, OPTIONAL);
+
+            retAttrs = ctx.getAttributes(KEY, new String[] { "* *" });
+            DNSTestUtils.verifySchema(retAttrs, MANDATORY, OPTIONAL);
+
+            retAttrs = ctx.getAttributes(KEY, new String[] { "IN *" });
+            DNSTestUtils.verifySchema(retAttrs, MANDATORY, OPTIONAL);
+
+        } finally {
+            DNSTestUtils.cleanup(ctx);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/dns/lib/DNSServer.java	Tue Feb 13 12:19:37 2018 +0800
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import sun.security.util.HexDumpEncoder;
+
+import javax.xml.bind.DatatypeConverter;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.nio.ByteBuffer;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Scanner;
+import java.util.regex.MatchResult;
+
+/*
+ * A dummy DNS server.
+ *
+ * Loads a sequence of DNS messages from a capture file into its cache.
+ * It listens for DNS UDP requests, finds match request in cache and sends the
+ * corresponding DNS responses.
+ *
+ * The capture file contains an DNS protocol exchange in the hexadecimal
+ * dump format emitted by HexDumpEncoder:
+ *
+ * xxxx: 00 11 22 33 44 55 66 77   88 99 aa bb cc dd ee ff  ................
+ *
+ * Typically, DNS protocol exchange is generated by DNSTracer who captures
+ * communication messages between DNS application program and real DNS server
+ */
+public class DNSServer implements Runnable {
+
+    public class Pair<F, S> {
+        private F first;
+        private S second;
+
+        public Pair(F first, S second) {
+            this.first = first;
+            this.second = second;
+        }
+
+        public void setFirst(F first) {
+            this.first = first;
+        }
+
+        public void setSecond(S second) {
+            this.second = second;
+        }
+
+        public F getFirst() {
+            return first;
+        }
+
+        public S getSecond() {
+            return second;
+        }
+    }
+
+    public static final int DNS_HEADER_SIZE = 12;
+    public static final int DNS_PACKET_SIZE = 512;
+
+    static HexDumpEncoder encoder = new HexDumpEncoder();
+
+    private DatagramSocket socket;
+    private String filename;
+    private boolean loop;
+    private final List<Pair<byte[], byte[]>> cache = new ArrayList<>();
+    private ByteBuffer reqBuffer = ByteBuffer.allocate(DNS_PACKET_SIZE);
+
+    public DNSServer(DatagramSocket socket, String filename) {
+        this(socket, filename, false);
+    }
+
+    public DNSServer(DatagramSocket socket, String filename, boolean loop) {
+        this.socket = socket;
+        this.filename = filename;
+        this.loop = loop;
+    }
+
+    public void run() {
+        try {
+            System.out.println(
+                    "DNSServer: Loading DNS cache data from : " + filename);
+            loadCaptureFile(filename);
+
+            System.out.println(
+                    "DNSServer: listening on port " + socket.getLocalPort());
+
+            System.out.println("DNSServer: loop playback: " + loop);
+
+            int playbackIndex = 0;
+
+            while (playbackIndex < cache.size()) {
+                DatagramPacket reqPacket = new DatagramPacket(reqBuffer.array(),
+                        reqBuffer.array().length);
+                socket.receive(reqPacket);
+
+                System.out.println(
+                        "DNSServer: received query message from " + reqPacket
+                                .getSocketAddress());
+
+                if (!verifyRequestMsg(reqPacket, playbackIndex)) {
+                    throw new RuntimeException(
+                            "DNSServer: Error: Failed to verify DNS request. "
+                                    + "Not identical request message : \n"
+                                    + encoder.encodeBuffer(
+                                    Arrays.copyOf(reqPacket.getData(),
+                                            reqPacket.getLength())));
+                }
+
+                byte[] payload = generateResponsePayload(reqPacket,
+                        playbackIndex);
+                socket.send(new DatagramPacket(payload, payload.length,
+                        reqPacket.getSocketAddress()));
+                System.out.println(
+                        "DNSServer: send response message to " + reqPacket
+                                .getSocketAddress());
+
+                playbackIndex++;
+                if (loop && playbackIndex >= cache.size()) {
+                    playbackIndex = 0;
+                }
+            }
+
+            System.out.println(
+                    "DNSServer: Done for all cached messages playback");
+        } catch (Exception e) {
+            System.err.println("DNSServer: Error: " + e);
+        }
+    }
+
+    /*
+     * Load a capture file containing an DNS protocol exchange in the
+     * hexadecimal dump format emitted by sun.misc.HexDumpEncoder:
+     *
+     * xxxx: 00 11 22 33 44 55 66 77   88 99 aa bb cc dd ee ff  ................
+     */
+    private void loadCaptureFile(String filename) throws IOException {
+        StringBuilder hexString = new StringBuilder();
+        String pattern = "(....): (..) (..) (..) (..) (..) (..) (..) (..)   "
+                + "(..) (..) (..) (..) (..) (..) (..) (..).*";
+
+        try (Scanner fileScanner = new Scanner(Paths.get(filename))) {
+            while (fileScanner.hasNextLine()) {
+
+                try (Scanner lineScanner = new Scanner(
+                        fileScanner.nextLine())) {
+                    if (lineScanner.findInLine(pattern) == null) {
+                        continue;
+                    }
+                    MatchResult result = lineScanner.match();
+                    for (int i = 1; i <= result.groupCount(); i++) {
+                        String digits = result.group(i);
+                        if (digits.length() == 4) {
+                            if (digits.equals("0000")) { // start-of-message
+                                if (hexString.length() > 0) {
+                                    addToCache(hexString.toString());
+                                    hexString.delete(0, hexString.length());
+                                }
+                            }
+                            continue;
+                        } else if (digits.equals("  ")) { // short message
+                            continue;
+                        }
+                        hexString.append(digits);
+                    }
+                }
+            }
+        }
+        addToCache(hexString.toString());
+    }
+
+    /*
+     * Add an DNS encoding to the cache (by request message key).
+     */
+    private void addToCache(String hexString) {
+        byte[] encoding = DatatypeConverter.parseHexBinary(hexString);
+        if (encoding.length < DNS_HEADER_SIZE) {
+            throw new RuntimeException("Invalid DNS message : " + hexString);
+        }
+
+        if (getQR(encoding) == 0) {
+            // a query message, create entry in cache
+            cache.add(new Pair<>(encoding, null));
+            System.out.println(
+                    "    adding DNS query message with ID " + getID(encoding)
+                            + " to the cache");
+        } else {
+            // a response message, attach it to the query entry
+            if (!cache.isEmpty() && (getID(getLatestCacheEntry().getFirst())
+                    == getID(encoding))) {
+                getLatestCacheEntry().setSecond(encoding);
+                System.out.println(
+                        "    adding DNS response message associated to ID "
+                                + getID(encoding) + " in the cache");
+            } else {
+                throw new RuntimeException(
+                        "Invalid DNS message : " + hexString);
+            }
+        }
+    }
+
+    /*
+     * ID: A 16 bit identifier assigned by the program that generates any
+     * kind of query. This identifier is copied the corresponding reply and
+     * can be used by the requester to match up replies to outstanding queries.
+     */
+    private static int getID(byte[] encoding) {
+        return ByteBuffer.wrap(encoding, 0, 2).getShort();
+    }
+
+    /*
+     * QR: A one bit field that specifies whether this message is
+     * a query (0), or a response (1) after ID
+     */
+    private static int getQR(byte[] encoding) {
+        return encoding[2] & (0x01 << 7);
+    }
+
+    private Pair<byte[], byte[]> getLatestCacheEntry() {
+        return cache.get(cache.size() - 1);
+    }
+
+    private boolean verifyRequestMsg(DatagramPacket packet, int playbackIndex) {
+        byte[] cachedRequest = cache.get(playbackIndex).getFirst();
+        return Arrays.equals(Arrays
+                        .copyOfRange(packet.getData(), 2, packet.getLength()),
+                Arrays.copyOfRange(cachedRequest, 2, cachedRequest.length));
+    }
+
+    private byte[] generateResponsePayload(DatagramPacket packet,
+            int playbackIndex) {
+        byte[] resMsg = cache.get(playbackIndex).getSecond();
+        byte[] payload = Arrays.copyOf(resMsg, resMsg.length);
+
+        // replace the ID with same with real request
+        payload[0] = packet.getData()[0];
+        payload[1] = packet.getData()[1];
+
+        return payload;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/dns/lib/DNSTestUtils.java	Tue Feb 13 12:19:37 2018 +0800
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import java.io.PrintStream;
+import java.net.DatagramSocket;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Hashtable;
+
+public class DNSTestUtils {
+    public static final String TEST_DNS_SERVER_THREAD = "test.dns.server.thread";
+    public static final String TEST_DNS_ROOT_URL = "test.dns.root.url";
+    public static final int HOSTS_LOOKUP_MAX_DEPTH = 3;
+
+    protected static boolean debug = true;
+
+    /*
+     * Check that attrs contains the mandatory attributes and the right
+     * objectclass attribute
+     */
+    public static boolean checkSchema(Attributes attrs, String[] mandatory,
+            String[] optional) {
+        // Check mandatory attributes
+        for (String mandatoryAttr : mandatory) {
+            if (attrs.get(mandatoryAttr) == null) {
+                debug("missing mandatory attribute: " + mandatoryAttr);
+                return false;
+            }
+        }
+
+        // Check optional attributes
+        int optMissing = 0;
+        for (String optionalAttr : optional) {
+            if (attrs.get(optionalAttr) == null) {
+                debug("warning: missing optional attribute: " + optionalAttr);
+                ++optMissing;
+            }
+        }
+
+        if (attrs.size() > (mandatory.length + (optional.length
+                - optMissing))) {
+            debug("too many attributes: " + attrs);
+            return false;
+        }
+
+        return true;
+    }
+
+    /*
+     * Process command line arguments and init env
+     */
+    public static Hashtable<Object, Object> initEnv(DatagramSocket socket,
+            String testname, String[] args) {
+
+        Hashtable<Object, Object> env = new Hashtable<>();
+
+        // set some default parameters if no additional specified
+        env.put("DNS_DOMAIN", "domain1.com.");
+        env.put("FOREIGN_DOMAIN", "Central.Sun.COM.");
+        env.put("FOREIGN_LEAF", "sunweb");
+
+        // set defaults for some JNDI properties
+        env.put(Context.INITIAL_CONTEXT_FACTORY,
+                "com.sun.jndi.dns.DnsContextFactory");
+
+        boolean traceEnable = false;
+        boolean loopPlayback = false;
+        for (int i = 0; i < args.length; i++) {
+            if ((args[i].equals("-D")) && (args.length > i + 1)) {
+                extractProperty(args[++i], env);
+            } else if (args[i].startsWith("-D")) {
+                extractProperty(args[i].substring(2), env);
+            } else if (args[i].equalsIgnoreCase("-trace")) {
+                traceEnable = true;
+            } else if (args[i].equalsIgnoreCase("-loop")) {
+                loopPlayback = true;
+            }
+        }
+
+        debug = Boolean.valueOf(System.getProperty("debug", "true"));
+
+        if (env.get("DNS_SERVER") != null) {
+            String port = (String) env.get("DNS_PORT");
+            String portSuffix = (port == null) ? "" : ":" + port;
+            String url = "dns://" + env.get("DNS_SERVER") + portSuffix;
+            env.put(Context.PROVIDER_URL, url);
+            env.put(Context.PROVIDER_URL, url + "/" + env.get("DNS_DOMAIN"));
+        }
+
+        Runnable inst = null;
+        if (traceEnable) {
+            inst = createDNSTracer(socket, testname, env);
+        } else {
+            if (socket != null) {
+                inst = createDNSServer(socket, testname, loopPlayback);
+            } else {
+                // for tests which run against remote server
+                // or no server required
+                debug("Skip local DNS Server creation "
+                        + "since DatagramSocket is null");
+            }
+        }
+
+        if (inst != null) {
+            env.put(TEST_DNS_SERVER_THREAD, startServer(inst));
+            String url = "dns://localhost:" + socket.getLocalPort();
+
+            env.put(TEST_DNS_ROOT_URL, url);
+            env.put(Context.PROVIDER_URL, url + "/" + env.get("DNS_DOMAIN"));
+        }
+
+        return env;
+    }
+
+    /*
+     * Clean-up the directory context.
+     */
+    public static void cleanup(Context ctx) {
+        if (ctx != null) {
+            try {
+                ctx.close();
+            } catch (NamingException e) {
+                // ignore
+            }
+        }
+    }
+
+    private static void extractProperty(String propString,
+            Hashtable<Object, Object> env) {
+        int index;
+
+        if ((index = propString.indexOf('=')) > 0) {
+            env.put(propString.substring(0, index),
+                    propString.substring(index + 1));
+        } else {
+            throw new RuntimeException(
+                    "Failed to extract test args property from " + propString);
+        }
+    }
+
+    public static DNSTracer createDNSTracer(DatagramSocket socket,
+            String testname, Hashtable<Object, Object> env) {
+        if (socket == null) {
+            throw new RuntimeException("Error: failed to create DNSTracer "
+                    + "since DatagramSocket is null");
+        }
+
+        try {
+            PrintStream outStream = new PrintStream(getCaptureFile(testname));
+            return new DNSTracer(socket, outStream,
+                    (String) env.get("DNS_SERVER"),
+                    Integer.parseInt((String) env.get("DNS_PORT")));
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    "Error: failed to create DNSTracer : " + e.getMessage(), e);
+        }
+    }
+
+    public static DNSServer createDNSServer(DatagramSocket socket,
+            String testname, boolean loop) {
+        if (socket == null) {
+            throw new RuntimeException("Error: failed to create DNSServer "
+                    + "since DatagramSocket is null");
+        }
+
+        String path = getCaptureFile(testname);
+        if (Files.exists(Paths.get(path))) {
+            return new DNSServer(socket, path, loop);
+        } else {
+            throw new RuntimeException(
+                    "Error: failed to create DNSServer, not found dns "
+                            + "cache file " + path);
+        }
+    }
+
+    public static Thread startServer(Runnable runnable) {
+        Thread thread = new Thread(runnable);
+        thread.start();
+        return thread;
+    }
+
+    public static String getCaptureFile(String testname) {
+        return Paths.get(System.getProperty("test.src"))
+                .resolve(testname + ".dns").toString();
+    }
+
+    public static void enableHostsFile(String hostsFile) {
+        System.out.println("Enable jdk.net.hosts.file = " + hostsFile);
+        System.setProperty("jdk.net.hosts.file", hostsFile);
+    }
+
+    public static void enableHostsFile(int depth) {
+        Path path = Paths.get(System.getProperty("test.src", "."))
+                .toAbsolutePath();
+        for (int i = depth; i >= 0; i--) {
+            Path filePath = path.resolve("hosts");
+            if (Files.exists(filePath) && !Files.isDirectory(filePath)) {
+                enableHostsFile(filePath.toString());
+                break;
+            }
+
+            path = path.getParent();
+            if (path == null) {
+                break;
+            }
+        }
+    }
+
+    public static void debug(Object object) {
+        if (debug) {
+            System.out.println(object);
+        }
+    }
+
+    public static void verifySchema(Attributes attrs, String[] mandatory,
+            String[] optional) {
+        debug(attrs);
+        if (!checkSchema(attrs, mandatory, optional)) {
+            throw new RuntimeException("Check schema failed.");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/dns/lib/DNSTracer.java	Tue Feb 13 12:19:37 2018 +0800
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import sun.security.util.HexDumpEncoder;
+
+import java.io.PrintStream;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/*
+ * A DNS UDP message tracer.
+ *
+ * It listens for DNS UDP requests, forward request to real DNS server, receives
+ * response message and sends back to requester, at same time dump all messages
+ * into capture file
+ *
+ * The capture file contains an DNS protocol exchange in the hexadecimal
+ * dump format emitted by HexDumpEncoder:
+ *
+ * xxxx: 00 11 22 33 44 55 66 77   88 99 aa bb cc dd ee ff  ................
+ *
+ * Typically, the capture file data will be used by DNSServer for playback
+ */
+public class DNSTracer implements Runnable {
+    public static final int DNS_DEFAULT_PORT = 53;
+    public static final int DNS_PACKET_SIZE = 512;
+    static HexDumpEncoder encoder = new HexDumpEncoder();
+
+    private DatagramSocket inSocket;
+    private SocketAddress dnsServerAddress;
+    private ByteBuffer reqBuffer = ByteBuffer.allocate(DNS_PACKET_SIZE);
+    private ByteBuffer resBuffer = ByteBuffer.allocate(DNS_PACKET_SIZE);
+    private PrintStream out = null;
+
+    public DNSTracer(DatagramSocket socket, String dnsHostname) {
+        this(socket, dnsHostname, DNS_DEFAULT_PORT);
+    }
+
+    public DNSTracer(DatagramSocket socket, PrintStream outStream,
+            String dnsHostname) {
+        this(socket, outStream, dnsHostname, DNS_DEFAULT_PORT);
+    }
+
+    public DNSTracer(DatagramSocket socket, String dnsHostname, int dnsPort) {
+        this(socket, System.out, dnsHostname, dnsPort);
+    }
+
+    public DNSTracer(DatagramSocket socket, PrintStream outStream,
+            String dnsHostname, int dnsPort) {
+        inSocket = socket;
+        out = outStream;
+        dnsServerAddress = new InetSocketAddress(dnsHostname, dnsPort);
+    }
+
+    public void run() {
+        System.out.println(
+                "DNSTracer: listening on port " + inSocket.getLocalPort());
+
+        System.out.println("DNSTracer: will forward request to server "
+                + dnsServerAddress);
+
+        try (DatagramSocket outSocket = new DatagramSocket()) {
+            while (true) {
+                DatagramPacket reqPacket = new DatagramPacket(reqBuffer.array(),
+                        reqBuffer.array().length);
+                inSocket.receive(reqPacket);
+
+                out.println("-> " + reqPacket.getSocketAddress());
+                out.println();
+                // dump dns request data
+                out.println(encoder.encodeBuffer(
+                        Arrays.copyOf(reqPacket.getData(),
+                                reqPacket.getLength())));
+                out.println();
+
+                outSocket.send(new DatagramPacket(reqPacket.getData(),
+                        reqPacket.getLength(), dnsServerAddress));
+                DatagramPacket resPacket = new DatagramPacket(resBuffer.array(),
+                        resBuffer.array().length);
+                outSocket.receive(resPacket);
+
+                out.println("<- " + resPacket.getSocketAddress());
+                out.println();
+                // dump dns response data
+                out.println(encoder.encodeBuffer(
+                        Arrays.copyOf(resPacket.getData(),
+                                resPacket.getLength())));
+                out.println();
+
+                inSocket.send(new DatagramPacket(resPacket.getData(),
+                        resPacket.getLength(), reqPacket.getSocketAddress()));
+            }
+        } catch (SocketException se) {
+            if (inSocket.isClosed()) {
+                out.flush();
+                System.out.println("DNSTracer: Exit");
+            } else {
+                se.printStackTrace();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+}