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 java.io.PrintStream;
  27 import java.net.DatagramPacket;
  28 import java.net.DatagramSocket;
  29 import java.net.InetAddress;
  30 import java.net.InetSocketAddress;
  31 import java.net.SocketAddress;
  32 import java.net.SocketException;
  33 import java.nio.ByteBuffer;
  34 import java.util.Arrays;
  35 
  36 /*
  37  * A DNS UDP message tracer.
  38  *
  39  * It listens for DNS UDP requests, forward request to real DNS server, receives
  40  * response message and sends back to requester, at same time dump all messages
  41  * into capture file
  42  *
  43  * The capture file contains an DNS protocol exchange in the hexadecimal
  44  * dump format emitted by HexDumpEncoder:
  45  *
  46  * xxxx: 00 11 22 33 44 55 66 77   88 99 aa bb cc dd ee ff  ................
  47  *
  48  * Typically, the capture file data will be used by DNSServer for playback
  49  */
  50 public class DNSTracer extends Thread implements Server {
  51     public static final int DNS_DEFAULT_PORT = 53;
  52     public static final int DNS_PACKET_SIZE = 512;
  53     static HexDumpEncoder encoder = new HexDumpEncoder();
  54 
  55     private DatagramSocket inSocket;
  56     private SocketAddress dnsServerAddress;
  57     private ByteBuffer reqBuffer = ByteBuffer.allocate(DNS_PACKET_SIZE);
  58     private ByteBuffer resBuffer = ByteBuffer.allocate(DNS_PACKET_SIZE);
  59     private PrintStream out = null;
  60     private volatile boolean isRunning;
  61 
  62     public DNSTracer(String dnsHostname) throws SocketException {
  63         this(dnsHostname, DNS_DEFAULT_PORT);
  64     }
  65 
  66     public DNSTracer(PrintStream outStream, String dnsHostname)
  67             throws SocketException {
  68         this(outStream, dnsHostname, DNS_DEFAULT_PORT);
  69     }
  70 
  71     public DNSTracer(String dnsHostname, int dnsPort) throws SocketException {
  72         this(System.out, dnsHostname, dnsPort);
  73     }
  74 
  75     public DNSTracer(PrintStream outStream, String dnsHostname, int dnsPort)
  76             throws SocketException {
  77         inSocket = new DatagramSocket(0, InetAddress.getLoopbackAddress());
  78         out = outStream;
  79         dnsServerAddress = new InetSocketAddress(dnsHostname, dnsPort);
  80     }
  81 
  82     public void run() {
  83         isRunning = true;
  84         System.out.println(
  85                 "DNSTracer: listening on port " + inSocket.getLocalPort());
  86 
  87         System.out.println("DNSTracer: will forward request to server "
  88                 + dnsServerAddress);
  89 
  90         try (DatagramSocket outSocket = new DatagramSocket()) {
  91             while (true) {
  92                 DatagramPacket reqPacket = new DatagramPacket(reqBuffer.array(),
  93                         reqBuffer.array().length);
  94                 inSocket.receive(reqPacket);
  95 
  96                 out.println("-> " + reqPacket.getSocketAddress());
  97                 out.println();
  98                 // dump dns request data
  99                 out.println(encoder.encodeBuffer(
 100                         Arrays.copyOf(reqPacket.getData(),
 101                                 reqPacket.getLength())));
 102                 out.println();
 103 
 104                 outSocket.send(new DatagramPacket(reqPacket.getData(),
 105                         reqPacket.getLength(), dnsServerAddress));
 106                 DatagramPacket resPacket = new DatagramPacket(resBuffer.array(),
 107                         resBuffer.array().length);
 108                 outSocket.receive(resPacket);
 109 
 110                 out.println("<- " + resPacket.getSocketAddress());
 111                 out.println();
 112                 // dump dns response data
 113                 out.println(encoder.encodeBuffer(
 114                         Arrays.copyOf(resPacket.getData(),
 115                                 resPacket.getLength())));
 116                 out.println();
 117 
 118                 inSocket.send(new DatagramPacket(resPacket.getData(),
 119                         resPacket.getLength(), reqPacket.getSocketAddress()));
 120             }
 121         } catch (SocketException se) {
 122             if (!isRunning) {
 123                 out.flush();
 124                 System.out.println("DNSTracer: Exit");
 125             } else {
 126                 se.printStackTrace();
 127             }
 128         } catch (Exception e) {
 129             e.printStackTrace();
 130         }
 131     }
 132 
 133     @Override public void stopServer() {
 134         isRunning = false;
 135         if (inSocket != null) {
 136             try {
 137                 inSocket.close();
 138             } catch (Exception e) {
 139                 // ignore
 140             }
 141         }
 142     }
 143 
 144     @Override public int getPort() {
 145         if (inSocket != null) {
 146             return inSocket.getLocalPort();
 147         } else {
 148             return -1;
 149         }
 150     }
 151 }
--- EOF ---