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 
DNSTracer(String dnsHostname)62     public DNSTracer(String dnsHostname) throws SocketException {
63         this(dnsHostname, DNS_DEFAULT_PORT);
64     }
65 
DNSTracer(PrintStream outStream, String dnsHostname)66     public DNSTracer(PrintStream outStream, String dnsHostname)
67             throws SocketException {
68         this(outStream, dnsHostname, DNS_DEFAULT_PORT);
69     }
70 
DNSTracer(String dnsHostname, int dnsPort)71     public DNSTracer(String dnsHostname, int dnsPort) throws SocketException {
72         this(System.out, dnsHostname, dnsPort);
73     }
74 
DNSTracer(PrintStream outStream, String dnsHostname, int dnsPort)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 
run()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 
stopServer()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 
getPort()144     @Override public int getPort() {
145         if (inSocket != null) {
146             return inSocket.getLocalPort();
147         } else {
148             return -1;
149         }
150     }
151 }
152