Path: blob/master/test/jdk/com/sun/jndi/dns/lib/DNSServer.java
41155 views
/*1* Copyright (c) 2018, 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.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import sun.security.util.HexDumpEncoder;2425import java.io.IOException;26import java.net.DatagramPacket;27import java.net.DatagramSocket;28import java.net.InetAddress;29import java.net.SocketException;30import java.nio.ByteBuffer;31import java.nio.file.Paths;32import java.util.ArrayList;33import java.util.Arrays;34import java.util.List;35import java.util.Scanner;36import java.util.regex.MatchResult;3738/*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 the43* corresponding DNS responses.44*45* The capture file contains an DNS protocol exchange in the hexadecimal46* 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 captures51* communication messages between DNS application program and real DNS server52*/53public class DNSServer extends Thread implements Server {5455public class Pair<F, S> {56private F first;57private S second;5859public Pair(F first, S second) {60this.first = first;61this.second = second;62}6364public void setFirst(F first) {65this.first = first;66}6768public void setSecond(S second) {69this.second = second;70}7172public F getFirst() {73return first;74}7576public S getSecond() {77return second;78}79}8081public static final int DNS_HEADER_SIZE = 12;82public static final int DNS_PACKET_SIZE = 512;8384static HexDumpEncoder encoder = new HexDumpEncoder();8586private DatagramSocket socket;87private String filename;88private boolean loop;89private final List<Pair<byte[], byte[]>> cache = new ArrayList<>();90private ByteBuffer reqBuffer = ByteBuffer.allocate(DNS_PACKET_SIZE);91private volatile boolean isRunning;9293public DNSServer(String filename) throws SocketException {94this(filename, false);95}9697public DNSServer(String filename, boolean loop) throws SocketException {98this.socket = new DatagramSocket(0, InetAddress.getLoopbackAddress());99this.filename = filename;100this.loop = loop;101}102103public void run() {104try {105isRunning = true;106System.out.println(107"DNSServer: Loading DNS cache data from : " + filename);108loadCaptureFile(filename);109110System.out.println(111"DNSServer: listening on port " + socket.getLocalPort());112113System.out.println("DNSServer: loop playback: " + loop);114115int playbackIndex = 0;116117while (playbackIndex < cache.size()) {118DatagramPacket reqPacket = receiveQuery();119120if (!verifyRequestMsg(reqPacket, playbackIndex)) {121if (playbackIndex > 0 && verifyRequestMsg(reqPacket,122playbackIndex - 1)) {123System.out.println(124"DNSServer: received retry query, resend");125playbackIndex--;126} else {127throw new RuntimeException(128"DNSServer: Error: Failed to verify DNS request. "129+ "Not identical request message : \n"130+ encoder.encodeBuffer(131Arrays.copyOf(reqPacket.getData(),132reqPacket.getLength())));133}134}135136sendResponse(reqPacket, playbackIndex);137138playbackIndex++;139if (loop && playbackIndex >= cache.size()) {140playbackIndex = 0;141}142}143144System.out.println(145"DNSServer: Done for all cached messages playback");146147System.out.println(148"DNSServer: Still listening for possible retry query");149while (true) {150DatagramPacket reqPacket = receiveQuery();151152// here we only handle the retry query for last one153if (!verifyRequestMsg(reqPacket, playbackIndex - 1)) {154throw new RuntimeException(155"DNSServer: Error: Failed to verify DNS request. "156+ "Not identical request message : \n"157+ encoder.encodeBuffer(158Arrays.copyOf(reqPacket.getData(),159reqPacket.getLength())));160}161162sendResponse(reqPacket, playbackIndex - 1);163}164} catch (Exception e) {165if (isRunning) {166System.err.println("DNSServer: Error: " + e);167e.printStackTrace();168} else {169System.out.println("DNSServer: Exit");170}171}172}173174private DatagramPacket receiveQuery() throws IOException {175DatagramPacket reqPacket = new DatagramPacket(reqBuffer.array(),176reqBuffer.array().length);177socket.receive(reqPacket);178179System.out.println("DNSServer: received query message from " + reqPacket180.getSocketAddress());181182return reqPacket;183}184185private void sendResponse(DatagramPacket reqPacket, int playbackIndex)186throws IOException {187byte[] payload = generateResponsePayload(reqPacket, playbackIndex);188socket.send(new DatagramPacket(payload, payload.length,189reqPacket.getSocketAddress()));190System.out.println("DNSServer: send response message to " + reqPacket191.getSocketAddress());192}193194/*195* Load a capture file containing an DNS protocol exchange in the196* hexadecimal dump format emitted by sun.misc.HexDumpEncoder:197*198* xxxx: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ................199*/200private void loadCaptureFile(String filename) throws IOException {201StringBuilder hexString = new StringBuilder();202String pattern = "(....): (..) (..) (..) (..) (..) (..) (..) (..) "203+ "(..) (..) (..) (..) (..) (..) (..) (..).*";204205try (Scanner fileScanner = new Scanner(Paths.get(filename))) {206while (fileScanner.hasNextLine()) {207208try (Scanner lineScanner = new Scanner(209fileScanner.nextLine())) {210if (lineScanner.findInLine(pattern) == null) {211continue;212}213MatchResult result = lineScanner.match();214for (int i = 1; i <= result.groupCount(); i++) {215String digits = result.group(i);216if (digits.length() == 4) {217if (digits.equals("0000")) { // start-of-message218if (hexString.length() > 0) {219addToCache(hexString.toString());220hexString.delete(0, hexString.length());221}222}223continue;224} else if (digits.equals(" ")) { // short message225continue;226}227hexString.append(digits);228}229}230}231}232addToCache(hexString.toString());233}234235/*236* Add an DNS encoding to the cache (by request message key).237*/238private void addToCache(String hexString) {239byte[] encoding = parseHexBinary(hexString);240if (encoding.length < DNS_HEADER_SIZE) {241throw new RuntimeException("Invalid DNS message : " + hexString);242}243244if (getQR(encoding) == 0) {245// a query message, create entry in cache246cache.add(new Pair<>(encoding, null));247System.out.println(248" adding DNS query message with ID " + getID(encoding)249+ " to the cache");250} else {251// a response message, attach it to the query entry252if (!cache.isEmpty() && (getID(getLatestCacheEntry().getFirst())253== getID(encoding))) {254getLatestCacheEntry().setSecond(encoding);255System.out.println(256" adding DNS response message associated to ID "257+ getID(encoding) + " in the cache");258} else {259throw new RuntimeException(260"Invalid DNS message : " + hexString);261}262}263}264265/*266* ID: A 16 bit identifier assigned by the program that generates any267* kind of query. This identifier is copied the corresponding reply and268* can be used by the requester to match up replies to outstanding queries.269*/270private static int getID(byte[] encoding) {271return ByteBuffer.wrap(encoding, 0, 2).getShort();272}273274/*275* QR: A one bit field that specifies whether this message is276* a query (0), or a response (1) after ID277*/278private static int getQR(byte[] encoding) {279return encoding[2] & (0x01 << 7);280}281282private Pair<byte[], byte[]> getLatestCacheEntry() {283return cache.get(cache.size() - 1);284}285286private boolean verifyRequestMsg(DatagramPacket packet, int playbackIndex) {287byte[] cachedRequest = cache.get(playbackIndex).getFirst();288return Arrays.equals(Arrays289.copyOfRange(packet.getData(), 2, packet.getLength()),290Arrays.copyOfRange(cachedRequest, 2, cachedRequest.length));291}292293private byte[] generateResponsePayload(DatagramPacket packet,294int playbackIndex) {295byte[] resMsg = cache.get(playbackIndex).getSecond();296byte[] payload = Arrays.copyOf(resMsg, resMsg.length);297298// replace the ID with same with real request299payload[0] = packet.getData()[0];300payload[1] = packet.getData()[1];301302return payload;303}304305public static byte[] parseHexBinary(String s) {306307final int len = s.length();308309// "111" is not a valid hex encoding.310if (len % 2 != 0) {311throw new IllegalArgumentException(312"hexBinary needs to be even-length: " + s);313}314315byte[] out = new byte[len / 2];316317for (int i = 0; i < len; i += 2) {318int h = hexToBin(s.charAt(i));319int l = hexToBin(s.charAt(i + 1));320if (h == -1 || l == -1) {321throw new IllegalArgumentException(322"contains illegal character for hexBinary: " + s);323}324325out[i / 2] = (byte) (h * 16 + l);326}327328return out;329}330331private static int hexToBin(char ch) {332if ('0' <= ch && ch <= '9') {333return ch - '0';334}335if ('A' <= ch && ch <= 'F') {336return ch - 'A' + 10;337}338if ('a' <= ch && ch <= 'f') {339return ch - 'a' + 10;340}341return -1;342}343344@Override public void stopServer() {345isRunning = false;346if (socket != null) {347try {348socket.close();349} catch (Exception e) {350// ignore351}352}353}354355@Override public int getPort() {356if (socket != null) {357return socket.getLocalPort();358} else {359return -1;360}361}362}363364365