package com.koushikdutta.async.dns; import com.koushikdutta.async.AsyncDatagramSocket; import com.koushikdutta.async.AsyncServer; import com.koushikdutta.async.ByteBufferList; import com.koushikdutta.async.DataEmitter; import com.koushikdutta.async.callback.DataCallback; import com.koushikdutta.async.future.Cancellable; import com.koushikdutta.async.future.Future; import com.koushikdutta.async.future.FutureCallback; import com.koushikdutta.async.future.SimpleFuture; import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.DatagramSocket; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Random; /** * Created by koush on 10/20/13. */ public class Dns { public static Future<DnsResponse> lookup(String host) { return lookup(AsyncServer.getDefault(), host, false, null); } private static int setFlag(int flags, int value, int offset) { return flags | (value << offset); } private static int setQuery(int flags) { return setFlag(flags, 0, 0); } private static int setRecursion(int flags) { return setFlag(flags, 1, 8); } private static void addName(ByteBuffer bb, String name) { String[] parts = name.split("\\."); for (String part: parts) { bb.put((byte)part.length()); bb.put(part.getBytes()); } bb.put((byte)0); } private static String parseName(ByteBufferList bb, ByteBuffer backReference) { bb.order(ByteOrder.BIG_ENDIAN); String ret = ""; int len; while (0 != (len = bb.get() & 0x00FF)) { // compressed if ((len & 0x00c0) == 0x00c0) { int offset = ((len & ~0xFFFFFFc0) << 8) | (bb.get() & 0x00FF); if (ret.length() > 0) ret += "."; ByteBufferList sub = new ByteBufferList(); ByteBuffer duplicate = backReference.duplicate(); duplicate.get(new byte[offset]); sub.add(duplicate); return ret + parseName(sub, backReference); } byte[] bytes = new byte[len]; bb.get(bytes); if (ret.length() > 0) ret += "."; ret += new String(bytes); } return ret; } private static InetAddress parseAddress(ByteBufferList bb) { return null; } private static DnsResponse parse(ByteBufferList bb) { ByteBuffer b = bb.getAll(); bb.add(b.duplicate()); // naive parsing... bb.order(ByteOrder.BIG_ENDIAN); // id bb.getShort(); // flags bb.getShort(); // number questions int questions = bb.getShort(); // number answer rr int answers = bb.getShort(); // number authority rr int authorities = bb.getShort(); // number additional rr int additionals = bb.getShort(); for (int i = 0; i < questions; i++) { parseName(bb, b); // type bb.getShort(); // class bb.getShort(); } DnsResponse response = new DnsResponse(); for (int i = 0; i < answers; i++) { String name = parseName(bb, b); // type int type = bb.getShort(); // class int clazz = bb.getShort(); // ttl int ttl = bb.getInt(); // length of address int length = bb.getShort(); try { if (type == 1) { // data byte[] data = new byte[length]; bb.get(data); response.addresses.add(InetAddress.getByAddress(data)); } else if (type == 0x000c) { response.names.add(parseName(bb, b)); } else if (type == 16) { ByteBufferList txt = new ByteBufferList(); bb.get(txt, length); response.parseTxt(txt); } else { bb.get(new byte[length]); } } catch (Exception e) { // e.printStackTrace(); } } // authorities for (int i = 0; i < authorities; i++) { String name = parseName(bb, b); // type int type = bb.getShort(); // class int clazz = bb.getShort(); // ttl int ttl = bb.getInt(); // length of address int length = bb.getShort(); try { bb.get(new byte[length]); } catch (Exception e) { // e.printStackTrace(); } } // additionals for (int i = 0; i < additionals; i++) { String name = parseName(bb, b); // type int type = bb.getShort(); // class int clazz = bb.getShort(); // ttl int ttl = bb.getInt(); // length of address int length = bb.getShort(); try { if (type == 16) { ByteBufferList txt = new ByteBufferList(); bb.get(txt, length); response.parseTxt(txt); } else { bb.get(new byte[length]); } } catch (Exception e) { // e.printStackTrace(); } } return response; } public static Future<DnsResponse> lookup(AsyncServer server, String host) { return lookup(server, host, false, null); } public static Cancellable multicastLookup(AsyncServer server, String host, FutureCallback<DnsResponse> callback) { return lookup(server, host, true, callback); } public static Cancellable multicastLookup(String host, FutureCallback<DnsResponse> callback) { return multicastLookup(AsyncServer.getDefault(), host, callback); } public static Future<DnsResponse> lookup(AsyncServer server, String host, final boolean multicast, final FutureCallback<DnsResponse> callback) { ByteBuffer packet = ByteBufferList.obtain(1024).order(ByteOrder.BIG_ENDIAN); short id = (short)new Random().nextInt(); short flags = (short)setQuery(0); if (!multicast) flags = (short)setRecursion(flags); packet.putShort(id); packet.putShort(flags); // number questions packet.putShort(multicast ? (short)1 : (short)2); // number answer rr packet.putShort((short)0); // number authority rr packet.putShort((short)0); // number additional rr packet.putShort((short)0); addName(packet, host); // query packet.putShort(multicast ? (short)12 : (short)1); // request internet address packet.putShort((short)1); if (!multicast) { addName(packet, host); // AAAA query packet.putShort((short) 28); // request internet address packet.putShort((short)1); } packet.flip(); try { final AsyncDatagramSocket dgram; // todo, use the dns server... if (!multicast) { dgram = server.connectDatagram(new InetSocketAddress("8.8.8.8", 53)); } else { // System.out.println("multicast dns..."); dgram = AsyncServer.getDefault().openDatagram(new InetSocketAddress(5353), true); Field field = DatagramSocket.class.getDeclaredField("impl"); field.setAccessible(true); Object impl = field.get(dgram.getSocket()); Method method = impl.getClass().getMethod("join", InetAddress.class); method.setAccessible(true); method.invoke(impl, InetAddress.getByName("224.0.0.251")); ((DatagramSocket)dgram.getSocket()).setBroadcast(true); } final SimpleFuture<DnsResponse> ret = new SimpleFuture<DnsResponse>() { @Override protected void cleanup() { super.cleanup(); // System.out.println("multicast dns cleanup..."); dgram.close(); } }; dgram.setDataCallback(new DataCallback() { @Override public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { try { // System.out.println(dgram.getRemoteAddress()); DnsResponse response = parse(bb); // System.out.println(response); response.source = dgram.getRemoteAddress(); if (!multicast) { dgram.close(); ret.setComplete(response); } else { callback.onCompleted(null, response); } } catch (Exception e) { } bb.recycle(); } }); if (!multicast) dgram.write(packet); else dgram.send(new InetSocketAddress("224.0.0.251", 5353), packet); return ret; } catch (Exception e) { SimpleFuture<DnsResponse> ret = new SimpleFuture<DnsResponse>(); ret.setComplete(e); if (multicast) callback.onCompleted(e, null); return ret; } } }