// // This program implements a simple DNS relay server. // // This program is for demonstration purposes within the Computer // Networking in the Faculty of informatics of Università della // Svizzera italiana. See http://www.inf.usi.ch/carzaniga/edu/ntw/ // // Author: Antonio Carzaniga (antonio.carzaniga@usi.ch) // // Copyright (C) 2018 Università della Svizzera italiana // // This is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Siena 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 for more details. // // See . // import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketAddress; import java.net.InetSocketAddress; import java.net.DatagramPacket; import java.util.HashMap; /** Utility functions to read data from a packet. * * A packet is just a sequence of "octects", meaning 8-bit data * units, meaning bytes in Java. As a general principle, we read * data starting at the given offset from the given buffer. The * numeric data is read in "network order", which is equivalent to * big-endian format. (See * https://en.wikipedia.org/wiki/Endianness#Big) */ class InetFormat { static int read_int32(byte [] buffer, int offset) { int result = read_byte(buffer, offset++); result = (result << 8) | read_byte(buffer, offset++); result = (result << 8) | read_byte(buffer, offset++); result = (result << 8) | read_byte(buffer, offset); return result; } static long read_uint32(byte [] buffer, int offset) { long result = read_byte(buffer, offset++); result = (result << 8) | read_byte(buffer, offset++); result = (result << 8) | read_byte(buffer, offset++); result = (result << 8) | read_byte(buffer, offset); return result; } static int read_uint16(byte [] buffer, int offset) { return (read_byte(buffer, offset) << 8) | read_byte(buffer, offset + 1); } static boolean read_bool(byte [] buffer, int byte_offset, int bit_offset) { byte mask = 1; mask <<= (7 - bit_offset); return (buffer[byte_offset] & mask) != 0; } static byte read_bitfield(byte [] buffer, int byte_offset, int bit_offset, int length) { byte mask = ~0; mask >>= (8 - length); mask &= buffer[byte_offset] >> (8 - length - bit_offset); return mask; } static int read_byte(byte [] buffer, int offset) { return buffer[offset] & 0xff; } } /** A Name as a sequence of labels, as defined in Section 3.1 of RFC 1035 */ class Name extends InetFormat { byte [][] labels; /** read a Name starting at the offset within the given buffer. * * @return the offset of the element in the buffer immediately following the Name */ public int read(byte [] buffer, int offset) { // a potential "pointer" to a previous name (see Section 4.1.4) int pointer_offset = -1; if (read_byte(buffer,offset) >= 192) { pointer_offset = offset + 2; offset = read_uint16(buffer, offset) & 0x3fff; } int count = 0; int i = offset; int llen = read_byte(buffer, i); while (llen > 0) { i += llen + 1; llen = read_byte(buffer, i); count += 1; } labels = new byte[count][]; for (i = 0; i < count; ++i) { llen = read_byte(buffer, offset); offset += 1; labels[i] = java.util.Arrays.copyOfRange(buffer, offset, offset + llen); offset += llen; } offset += 1; return (pointer_offset < 0) ? offset : pointer_offset; } public String toString() { int len = labels.length; for (int i = 0; i < labels.length; ++i) len += labels[i].length; byte [] name = new byte[len]; int k = 0; for (int i = 0; i < labels.length; ++i) { for (int j = 0; j < labels[i].length; ++j) name[k++] = labels[i][j]; name[k++] = (byte)0x2e; /* '.' */ } return new String(name); } } /** A Question in a DNS packet, as specified in Section 4.1.2 of RFC 1035. */ class Question extends InetFormat { Name name; int type; int clss; /** read a Question starting at the given offset within the given buffer. * * @return the offset of the element in the buffer immediately following the Question */ public int read(byte [] buffer, int offset) { name = new Name(); offset = name.read(buffer, offset); type = read_uint16(buffer, offset); offset += 2; clss = read_uint16(buffer, offset); offset += 2; return offset; } public void print() { System.out.println("Question:" + " name=" + name.toString() + " type=" + type + " clss=" + clss); } }; /** A Resource Record in a DNS packet, as specified in Section 4.1.3 of RFC 1035. */ class RR extends InetFormat { Name name; int type; int clss; long ttl; int rdlength; byte [] rdata; /** read a Resource Record starting at the given offset within the given buffer. * * @return the offset of the element in the buffer immediately following the RR */ public int read(byte [] buffer, int offset) { name = new Name(); offset = name.read(buffer, offset); type = read_uint16(buffer, offset); offset += 2; clss = read_uint16(buffer, offset); offset += 2; ttl = read_uint32(buffer, offset); offset += 2; rdlength = read_uint16(buffer, offset); offset += 2; rdata = java.util.Arrays.copyOfRange(buffer, offset, offset + rdlength); offset += rdlength; return offset; } public void print() { System.out.println("RR:" + " name=" + name.toString() + " type=" + type + " clss=" + clss + " ttl=" + ttl + " rdlength=" + rdlength); } }; /** DNS Message, as specified in Section 4.1 of RFC 1035. */ class DNSMessage extends InetFormat { int id; boolean qr; byte opcode; boolean aa; boolean tc; boolean rd; boolean ra; byte z; byte rcode; int qdcount; int ancount; int nscount; int arcount; Question [] qd; RR [] an; RR [] ns; public void print() { System.out.println("DNS packet:" + " id=" + id + " qr=" + qr + " opcode=" + opcode + " aa=" + aa + " tc=" + tc + " rd=" + rd + " ra=" + ra + " z=" + z + " rcode=" + rcode + " qdcount=" + qdcount + " ancount=" + ancount + " nscount=" + nscount + " arcount=" + arcount); if (qdcount > 0) { for (int i = 0; i < qdcount; ++i) qd[i].print(); } if (ancount > 0) { for (int i = 0; i < ancount; ++i) an[i].print(); } if (nscount > 0) { for (int i = 0; i < nscount; ++i) ns[i].print(); } } /** read a DNS Message starting at the given offset within the given buffer. */ public void read(byte [] buffer, int offset) { id = read_uint16(buffer, offset); offset += 2; qr = read_bool(buffer, offset, 0); opcode = read_bitfield(buffer, offset + 2, 1, 4); aa = read_bool(buffer, offset, 5); tc = read_bool(buffer, offset, 6); rd = read_bool(buffer, offset, 7); offset += 1; ra = read_bool(buffer, offset, 0); z = read_bitfield(buffer, offset, 1, 3); rcode = read_bitfield(buffer, offset, 4, 4); offset += 1; qdcount = read_uint16(buffer, offset); offset += 2; ancount = read_uint16(buffer, offset); offset += 2; nscount = read_uint16(buffer, offset); offset += 2; arcount = read_uint16(buffer, offset); offset += 2; if (qdcount > 0) { qd = new Question[qdcount]; for (int i = 0; i < qdcount; ++i) { qd[i] = new Question(); offset = qd[i].read(buffer, offset); } } if (ancount > 0) { an = new RR[ancount]; for (int i = 0; i < ancount; ++i) { an[i] = new RR(); offset = an[i].read(buffer, offset); } } if (nscount > 0) { ns = new RR[nscount]; for (int i = 0; i < nscount; ++i) { ns[i] = new RR(); offset = ns[i].read(buffer, offset); } } } } /** A simple DNS "relay" server. * * Serves DNS queries by simply relaying them to a root server. */ public class LocalDNS { static public void main(String [] args) { if (args.length != 2) { System.err.println("usage: LocalDNS "); System.exit(1); } int local_port; SocketAddress root_address; try { local_port = Integer.parseInt(args[0]); root_address = new InetSocketAddress(args[1], 53); try { byte [] buffer = new byte[65536]; DatagramPacket pkt = new DatagramPacket(buffer, buffer.length); DatagramSocket socket = new DatagramSocket(local_port); HashMap pending = new HashMap (); DNSMessage message = new DNSMessage(); while (true) { socket.receive(pkt); message.read(pkt.getData(), pkt.getOffset()); message.print(); if (message.qr == false /* QUERY */) { SocketAddress client_addr = pkt.getSocketAddress(); System.out.println("query from " + client_addr + " --> root server"); pending.put(message.id, client_addr); pkt.setSocketAddress(root_address); socket.send(pkt); } else /* RESPONSE */ { System.out.print("reply from " + pkt.getSocketAddress()); SocketAddress client_addr = pending.remove(message.id); if (client_addr == null) { System.out.println(": id " + message.id + " not found!"); } else { System.out.println(" --> " + client_addr); pkt.setSocketAddress(client_addr); socket.send(pkt); } } } } catch (Exception ex) { System.err.println(ex); System.err.println("Terminating LocalDNS loop."); } } catch (Exception ex) { System.err.println(ex); System.err.println("usage: LocalDNS "); System.exit(2); } } }