test/jdk/com/sun/jndi/dns/lib/DNSServer.java
changeset 48851 1d8f882f2b2f
child 49127 73385a708426
equal deleted inserted replaced
48850:4b9835e3215b 48851:1d8f882f2b2f
       
     1 /*
       
     2  * Copyright (c) 2018, 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.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 import sun.security.util.HexDumpEncoder;
       
    25 
       
    26 import javax.xml.bind.DatatypeConverter;
       
    27 import java.io.IOException;
       
    28 import java.net.DatagramPacket;
       
    29 import java.net.DatagramSocket;
       
    30 import java.nio.ByteBuffer;
       
    31 import java.nio.file.Paths;
       
    32 import java.util.ArrayList;
       
    33 import java.util.Arrays;
       
    34 import java.util.List;
       
    35 import java.util.Scanner;
       
    36 import java.util.regex.MatchResult;
       
    37 
       
    38 /*
       
    39  * A dummy DNS server.
       
    40  *
       
    41  * Loads a sequence of DNS messages from a capture file into its cache.
       
    42  * It listens for DNS UDP requests, finds match request in cache and sends the
       
    43  * corresponding DNS responses.
       
    44  *
       
    45  * The capture file contains an DNS protocol exchange in the hexadecimal
       
    46  * dump format emitted by HexDumpEncoder:
       
    47  *
       
    48  * xxxx: 00 11 22 33 44 55 66 77   88 99 aa bb cc dd ee ff  ................
       
    49  *
       
    50  * Typically, DNS protocol exchange is generated by DNSTracer who captures
       
    51  * communication messages between DNS application program and real DNS server
       
    52  */
       
    53 public class DNSServer implements Runnable {
       
    54 
       
    55     public class Pair<F, S> {
       
    56         private F first;
       
    57         private S second;
       
    58 
       
    59         public Pair(F first, S second) {
       
    60             this.first = first;
       
    61             this.second = second;
       
    62         }
       
    63 
       
    64         public void setFirst(F first) {
       
    65             this.first = first;
       
    66         }
       
    67 
       
    68         public void setSecond(S second) {
       
    69             this.second = second;
       
    70         }
       
    71 
       
    72         public F getFirst() {
       
    73             return first;
       
    74         }
       
    75 
       
    76         public S getSecond() {
       
    77             return second;
       
    78         }
       
    79     }
       
    80 
       
    81     public static final int DNS_HEADER_SIZE = 12;
       
    82     public static final int DNS_PACKET_SIZE = 512;
       
    83 
       
    84     static HexDumpEncoder encoder = new HexDumpEncoder();
       
    85 
       
    86     private DatagramSocket socket;
       
    87     private String filename;
       
    88     private boolean loop;
       
    89     private final List<Pair<byte[], byte[]>> cache = new ArrayList<>();
       
    90     private ByteBuffer reqBuffer = ByteBuffer.allocate(DNS_PACKET_SIZE);
       
    91 
       
    92     public DNSServer(DatagramSocket socket, String filename) {
       
    93         this(socket, filename, false);
       
    94     }
       
    95 
       
    96     public DNSServer(DatagramSocket socket, String filename, boolean loop) {
       
    97         this.socket = socket;
       
    98         this.filename = filename;
       
    99         this.loop = loop;
       
   100     }
       
   101 
       
   102     public void run() {
       
   103         try {
       
   104             System.out.println(
       
   105                     "DNSServer: Loading DNS cache data from : " + filename);
       
   106             loadCaptureFile(filename);
       
   107 
       
   108             System.out.println(
       
   109                     "DNSServer: listening on port " + socket.getLocalPort());
       
   110 
       
   111             System.out.println("DNSServer: loop playback: " + loop);
       
   112 
       
   113             int playbackIndex = 0;
       
   114 
       
   115             while (playbackIndex < cache.size()) {
       
   116                 DatagramPacket reqPacket = new DatagramPacket(reqBuffer.array(),
       
   117                         reqBuffer.array().length);
       
   118                 socket.receive(reqPacket);
       
   119 
       
   120                 System.out.println(
       
   121                         "DNSServer: received query message from " + reqPacket
       
   122                                 .getSocketAddress());
       
   123 
       
   124                 if (!verifyRequestMsg(reqPacket, playbackIndex)) {
       
   125                     throw new RuntimeException(
       
   126                             "DNSServer: Error: Failed to verify DNS request. "
       
   127                                     + "Not identical request message : \n"
       
   128                                     + encoder.encodeBuffer(
       
   129                                     Arrays.copyOf(reqPacket.getData(),
       
   130                                             reqPacket.getLength())));
       
   131                 }
       
   132 
       
   133                 byte[] payload = generateResponsePayload(reqPacket,
       
   134                         playbackIndex);
       
   135                 socket.send(new DatagramPacket(payload, payload.length,
       
   136                         reqPacket.getSocketAddress()));
       
   137                 System.out.println(
       
   138                         "DNSServer: send response message to " + reqPacket
       
   139                                 .getSocketAddress());
       
   140 
       
   141                 playbackIndex++;
       
   142                 if (loop && playbackIndex >= cache.size()) {
       
   143                     playbackIndex = 0;
       
   144                 }
       
   145             }
       
   146 
       
   147             System.out.println(
       
   148                     "DNSServer: Done for all cached messages playback");
       
   149         } catch (Exception e) {
       
   150             System.err.println("DNSServer: Error: " + e);
       
   151         }
       
   152     }
       
   153 
       
   154     /*
       
   155      * Load a capture file containing an DNS protocol exchange in the
       
   156      * hexadecimal dump format emitted by sun.misc.HexDumpEncoder:
       
   157      *
       
   158      * xxxx: 00 11 22 33 44 55 66 77   88 99 aa bb cc dd ee ff  ................
       
   159      */
       
   160     private void loadCaptureFile(String filename) throws IOException {
       
   161         StringBuilder hexString = new StringBuilder();
       
   162         String pattern = "(....): (..) (..) (..) (..) (..) (..) (..) (..)   "
       
   163                 + "(..) (..) (..) (..) (..) (..) (..) (..).*";
       
   164 
       
   165         try (Scanner fileScanner = new Scanner(Paths.get(filename))) {
       
   166             while (fileScanner.hasNextLine()) {
       
   167 
       
   168                 try (Scanner lineScanner = new Scanner(
       
   169                         fileScanner.nextLine())) {
       
   170                     if (lineScanner.findInLine(pattern) == null) {
       
   171                         continue;
       
   172                     }
       
   173                     MatchResult result = lineScanner.match();
       
   174                     for (int i = 1; i <= result.groupCount(); i++) {
       
   175                         String digits = result.group(i);
       
   176                         if (digits.length() == 4) {
       
   177                             if (digits.equals("0000")) { // start-of-message
       
   178                                 if (hexString.length() > 0) {
       
   179                                     addToCache(hexString.toString());
       
   180                                     hexString.delete(0, hexString.length());
       
   181                                 }
       
   182                             }
       
   183                             continue;
       
   184                         } else if (digits.equals("  ")) { // short message
       
   185                             continue;
       
   186                         }
       
   187                         hexString.append(digits);
       
   188                     }
       
   189                 }
       
   190             }
       
   191         }
       
   192         addToCache(hexString.toString());
       
   193     }
       
   194 
       
   195     /*
       
   196      * Add an DNS encoding to the cache (by request message key).
       
   197      */
       
   198     private void addToCache(String hexString) {
       
   199         byte[] encoding = DatatypeConverter.parseHexBinary(hexString);
       
   200         if (encoding.length < DNS_HEADER_SIZE) {
       
   201             throw new RuntimeException("Invalid DNS message : " + hexString);
       
   202         }
       
   203 
       
   204         if (getQR(encoding) == 0) {
       
   205             // a query message, create entry in cache
       
   206             cache.add(new Pair<>(encoding, null));
       
   207             System.out.println(
       
   208                     "    adding DNS query message with ID " + getID(encoding)
       
   209                             + " to the cache");
       
   210         } else {
       
   211             // a response message, attach it to the query entry
       
   212             if (!cache.isEmpty() && (getID(getLatestCacheEntry().getFirst())
       
   213                     == getID(encoding))) {
       
   214                 getLatestCacheEntry().setSecond(encoding);
       
   215                 System.out.println(
       
   216                         "    adding DNS response message associated to ID "
       
   217                                 + getID(encoding) + " in the cache");
       
   218             } else {
       
   219                 throw new RuntimeException(
       
   220                         "Invalid DNS message : " + hexString);
       
   221             }
       
   222         }
       
   223     }
       
   224 
       
   225     /*
       
   226      * ID: A 16 bit identifier assigned by the program that generates any
       
   227      * kind of query. This identifier is copied the corresponding reply and
       
   228      * can be used by the requester to match up replies to outstanding queries.
       
   229      */
       
   230     private static int getID(byte[] encoding) {
       
   231         return ByteBuffer.wrap(encoding, 0, 2).getShort();
       
   232     }
       
   233 
       
   234     /*
       
   235      * QR: A one bit field that specifies whether this message is
       
   236      * a query (0), or a response (1) after ID
       
   237      */
       
   238     private static int getQR(byte[] encoding) {
       
   239         return encoding[2] & (0x01 << 7);
       
   240     }
       
   241 
       
   242     private Pair<byte[], byte[]> getLatestCacheEntry() {
       
   243         return cache.get(cache.size() - 1);
       
   244     }
       
   245 
       
   246     private boolean verifyRequestMsg(DatagramPacket packet, int playbackIndex) {
       
   247         byte[] cachedRequest = cache.get(playbackIndex).getFirst();
       
   248         return Arrays.equals(Arrays
       
   249                         .copyOfRange(packet.getData(), 2, packet.getLength()),
       
   250                 Arrays.copyOfRange(cachedRequest, 2, cachedRequest.length));
       
   251     }
       
   252 
       
   253     private byte[] generateResponsePayload(DatagramPacket packet,
       
   254             int playbackIndex) {
       
   255         byte[] resMsg = cache.get(playbackIndex).getSecond();
       
   256         byte[] payload = Arrays.copyOf(resMsg, resMsg.length);
       
   257 
       
   258         // replace the ID with same with real request
       
   259         payload[0] = packet.getData()[0];
       
   260         payload[1] = packet.getData()[1];
       
   261 
       
   262         return payload;
       
   263     }
       
   264 }