├── README ├── .gitignore ├── mdns-core ├── src │ └── main │ │ └── java │ │ └── net │ │ └── cheney │ │ └── mdns │ │ ├── DNSMessage.java │ │ ├── CachedRecord.java │ │ ├── DNSQuery.java │ │ ├── MdnsListener.java │ │ ├── DNSRequestBuilder.java │ │ ├── Record.java │ │ ├── DNSResponseParser.java │ │ └── DNSResponse.java └── pom.xml └── pom.xml /README: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings/ 4 | target/ 5 | bin/ 6 | -------------------------------------------------------------------------------- /mdns-core/src/main/java/net/cheney/mdns/DNSMessage.java: -------------------------------------------------------------------------------- 1 | package net.cheney.mdns; 2 | 3 | public abstract class DNSMessage { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /mdns-core/src/main/java/net/cheney/mdns/CachedRecord.java: -------------------------------------------------------------------------------- 1 | package net.cheney.mdns; 2 | 3 | public class CachedRecord extends Record { 4 | 5 | public CachedRecord(Type type, Class recordClass, String name, int ttl) { 6 | super(type, recordClass, name, ttl); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /mdns-core/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | mdns 5 | net.cheney.mdns 6 | 0.0.1-SNAPSHOT 7 | 8 | 4.0.0 9 | net.cheney.mdns 10 | mdns-core 11 | mdns core 12 | 0.0.1-SNAPSHOT 13 | 14 | 15 | 16 | commons-lang 17 | commons-lang 18 | 19 | 20 | -------------------------------------------------------------------------------- /mdns-core/src/main/java/net/cheney/mdns/DNSQuery.java: -------------------------------------------------------------------------------- 1 | package net.cheney.mdns; 2 | 3 | public final class DNSQuery extends DNSMessage { 4 | 5 | private final String name; 6 | private final Record.Type type; 7 | private final Record.Class clazz; 8 | 9 | public DNSQuery(String name, Record.Type type, Record.Class clazz) { 10 | this.name = name; 11 | this.type = type; 12 | this.clazz = clazz; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return String.format("%s [type %s, class %s]", name, type, clazz); 18 | } 19 | 20 | public String name() { 21 | return name; 22 | } 23 | 24 | public Record.Type type() { 25 | return type; 26 | } 27 | 28 | public Record.Class clazz() { 29 | return clazz; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /mdns-core/src/main/java/net/cheney/mdns/MdnsListener.java: -------------------------------------------------------------------------------- 1 | package net.cheney.mdns; 2 | 3 | import java.io.IOException; 4 | import java.net.DatagramPacket; 5 | import java.net.InetAddress; 6 | import java.net.MulticastSocket; 7 | import java.nio.ByteBuffer; 8 | 9 | public class MdnsListener implements Runnable { 10 | 11 | private final MulticastSocket socket; 12 | 13 | public MdnsListener() throws IOException { 14 | socket = new MulticastSocket(5353); 15 | socket.joinGroup(InetAddress.getByName("224.0.0.251")); 16 | } 17 | 18 | public void run() { 19 | try { 20 | for( ;; ) { 21 | DatagramPacket p = new DatagramPacket(new byte[512], 512); 22 | socket.receive(p); 23 | ByteBuffer b = ByteBuffer.wrap(p.getData(), p.getOffset(), p.getLength()); 24 | DNSResponse response = DNSResponseParser.parse(b); 25 | System.out.println(response); 26 | } 27 | } catch (IOException e) { 28 | e.printStackTrace(); 29 | } finally { 30 | socket.close(); 31 | } 32 | } 33 | 34 | public static void main(String[] args) throws IOException { 35 | Thread t = new Thread(new MdnsListener()); 36 | t.run(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | net.cheney.mdns 5 | mdns 6 | mdns 7 | 0.0.1-SNAPSHOT 8 | Multicast DNS 9 | 10 | pom 11 | 12 | 13 | 1.6 14 | ${maven.compile.source} 15 | UTF-8 16 | 17 | 18 | 19 | mdns-core 20 | 21 | 22 | 23 | 24 | 25 | junit 26 | junit 27 | 4.13.1 28 | 29 | 30 | commons-lang 31 | commons-lang 32 | 2.4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | maven-compiler-plugin 42 | 43 | ${maven.compile.source} 44 | ${maven.compile.target} 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /mdns-core/src/main/java/net/cheney/mdns/DNSRequestBuilder.java: -------------------------------------------------------------------------------- 1 | package net.cheney.mdns; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | import org.apache.commons.lang.StringUtils; 6 | 7 | public class DNSRequestBuilder { 8 | 9 | private DNSRequestBuilder() { }; 10 | 11 | /* 12 | * The header contains the following fields: 13 | 14 | 1 1 1 1 1 1 15 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 16 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 17 | | ID | 18 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 19 | |QR| Opcode |AA|TC|RD|RA| Z | RCODE | 20 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 21 | | QDCOUNT | 22 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 23 | | ANCOUNT | 24 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 25 | | NSCOUNT | 26 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 27 | | ARCOUNT | 28 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 29 | */ 30 | public static ByteBuffer buildRequest(DNSQuery query) { 31 | ByteBuffer request = ByteBuffer.allocate(512); 32 | short id = 1; 33 | request.putShort(id); 34 | short flags = 0; 35 | request.putShort(flags); 36 | short qdcount = 1; 37 | request.putShort(qdcount); 38 | short ancount = 0; 39 | request.putShort(ancount); 40 | short nscount = 0; 41 | request.putShort(nscount); 42 | short arcount = 0; 43 | request.putShort(arcount); 44 | 45 | request.put(toQName(query.name())); 46 | 47 | request.putShort(query.type().value()); 48 | request.putShort(query.clazz().value()); 49 | return (ByteBuffer) request.flip(); 50 | } 51 | 52 | private static ByteBuffer toQName(String string) { 53 | ByteBuffer buffer = ByteBuffer.allocate(512); 54 | for(String label : StringUtils.split(string, '.')) { 55 | buffer.put((byte) label.length()); 56 | for(char c : label.toCharArray()) { 57 | buffer.put((byte) c); 58 | } 59 | } 60 | buffer.put((byte) 0); 61 | return (ByteBuffer) buffer.flip(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /mdns-core/src/main/java/net/cheney/mdns/Record.java: -------------------------------------------------------------------------------- 1 | package net.cheney.mdns; 2 | 3 | public abstract class Record { 4 | 5 | private final String name; 6 | private final Type type; 7 | private final Class clazz; 8 | private final int ttl; 9 | 10 | public Record(Type type, Class recordClass, String name, int ttl) { 11 | this.name = name; 12 | this.type = type; 13 | this.clazz = recordClass; 14 | this.ttl = ttl; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return String.format("%s ttl: %d [type %s, clazz %s]", name, ttl, type, clazz); 20 | } 21 | 22 | public final String name() { 23 | return name; 24 | } 25 | 26 | public final Type type() { 27 | return type; 28 | } 29 | 30 | public final Class clazz() { 31 | return clazz; 32 | } 33 | 34 | public enum Class { 35 | 36 | IN(1), 37 | CS(2), 38 | CH(3), 39 | HS(4), 40 | 41 | ANY(255) 42 | ; 43 | 44 | private final short value; 45 | 46 | Class(int value) { 47 | this.value = (short) value; 48 | } 49 | 50 | public static Class parse(short i) { 51 | for(Class t : values()) { 52 | if(t.value == (i & 0x00ff)) return t; 53 | } 54 | return null; 55 | } 56 | 57 | public short value() { 58 | return value; 59 | } 60 | } 61 | 62 | // see http://en.wikipedia.org/wiki/List_of_DNS_record_types 63 | 64 | public enum Type { 65 | 66 | A(1), 67 | AAAA(28), 68 | AFSDB(18), 69 | CERT(37), 70 | DHCID(49), 71 | DLV(32769), 72 | DNAME(39), 73 | DNSKEY(48), 74 | DS(43), 75 | HIP(55), 76 | IPSECKEY(45), 77 | KEY(25), 78 | LOC(29), 79 | NAPTR(35), 80 | NSEC(47), 81 | NSEC3(50), 82 | NSEC3PARAM(51), 83 | RRSIG(46), 84 | SIG(24), 85 | SPF(99), 86 | SRV(33), 87 | SSHFP(44), 88 | TA(32768), 89 | NS(2), 90 | MD(3), 91 | MF(4), 92 | CNAME(5), 93 | SOA(6), 94 | MB(7), 95 | MG(8), 96 | MR(9), 97 | NULL(10), 98 | WKS(11), 99 | PTR(12), 100 | HINFO(13), 101 | MINFO(14), 102 | MX(15), 103 | TXT(16), 104 | 105 | OPT(41), 106 | 107 | TKEY(249), 108 | TSIG(250), 109 | 110 | IXFR(251), 111 | AXFR(252), 112 | MAILB(253), 113 | MAILA(254), 114 | 115 | ANY(255) 116 | ; 117 | 118 | private final short value; 119 | 120 | Type(int value) { 121 | this.value = (short) value; 122 | } 123 | 124 | public static Type parse(short i) { 125 | for(Type t : values()) { 126 | if(t.value == i) return t; 127 | } 128 | return null; 129 | } 130 | 131 | public short value() { 132 | return value; 133 | } 134 | } 135 | 136 | 137 | } 138 | -------------------------------------------------------------------------------- /mdns-core/src/main/java/net/cheney/mdns/DNSResponseParser.java: -------------------------------------------------------------------------------- 1 | package net.cheney.mdns; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import net.cheney.mdns.DNSResponse.Builder; 8 | 9 | import org.apache.commons.lang.StringUtils; 10 | 11 | public class DNSResponseParser { 12 | 13 | private DNSResponseParser() { }; 14 | 15 | public static DNSResponse parse(ByteBuffer buffer) { 16 | short id = buffer.getShort(); 17 | short flags = buffer.getShort(); 18 | short qdcount = buffer.getShort(), ancount = buffer.getShort(), nscount = buffer.getShort(), arcount = buffer.getShort(); 19 | 20 | Builder builder = DNSResponse.builder(id, flags); 21 | 22 | for(int i = 0 ; i < qdcount ; i++ ) { 23 | DNSQuery question = parseQuestion(buffer); 24 | builder.addQuestion(question); 25 | } 26 | 27 | for(int i = 0 ; i < ancount ; i++ ) { 28 | Record answer = parseResponseRecord(buffer); 29 | builder.addAnswer(answer); 30 | } 31 | 32 | for(int i = 0 ; i < nscount ; i++ ) { 33 | Record nameserver = parseResponseRecord(buffer); 34 | builder.addNameserver(nameserver); 35 | } 36 | 37 | for(int i = 0 ; i < arcount ; i++ ) { 38 | Record additionalRecord = parseResponseRecord(buffer); 39 | builder.addAdditionalRecord(additionalRecord); 40 | } 41 | 42 | return builder.build(); 43 | } 44 | 45 | private static DNSQuery parseQuestion(ByteBuffer buffer) { 46 | String name = parseName(buffer); 47 | Record.Type type = Record.Type.parse(buffer.getShort()); 48 | Record.Class clazz = Record.Class.parse(buffer.getShort()); 49 | return new DNSQuery(name, type, clazz); 50 | } 51 | 52 | private static String parseName(ByteBuffer buffer) { 53 | return StringUtils.join(parseName0(buffer), '.'); 54 | } 55 | 56 | private static List parseName0(ByteBuffer buffer) { 57 | List labels = new ArrayList(); 58 | for( ;; ) { 59 | byte length = buffer.get(); 60 | if(length != 0) { 61 | if ((length & 192) == 192) { 62 | // inelegant support for pointers 63 | int offset = (length & 63); 64 | offset =+ (buffer.get() & 0xff); 65 | labels.addAll(parseName0((ByteBuffer) buffer.duplicate().position(offset))); 66 | break; 67 | } else { 68 | StringBuilder label = new StringBuilder(63); 69 | for(int i = 0 ; i < length ; i++) { 70 | label.append((char)buffer.get()); 71 | } 72 | labels.add(label.toString()); 73 | } 74 | } else { 75 | break; 76 | } 77 | } 78 | return labels; 79 | } 80 | 81 | private static Record parseResponseRecord(ByteBuffer buffer) { 82 | String name = parseName(buffer); 83 | short type = buffer.getShort(); 84 | short clazz = buffer.getShort(); 85 | int ttl = buffer.getInt(); 86 | short rdlength = buffer.getShort(); 87 | ByteBuffer rdata = (ByteBuffer) buffer.duplicate().position(buffer.position()).limit(buffer.position() + rdlength); 88 | buffer.position(buffer.position() + rdlength); 89 | return new CachedRecord(Record.Type.parse(type), Record.Class.parse(clazz), name, ttl); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /mdns-core/src/main/java/net/cheney/mdns/DNSResponse.java: -------------------------------------------------------------------------------- 1 | package net.cheney.mdns; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import static java.lang.String.format; 7 | 8 | public class DNSResponse extends DNSMessage { 9 | 10 | private final short id; 11 | private final short flags; 12 | private final List questions; 13 | private final List answers, nameservers, additionalRecords; 14 | 15 | DNSResponse(short id, short flags, List questions, 16 | List answers, List nameservers, 17 | List additionalRecords) { 18 | this.id = id; 19 | this.flags = flags; 20 | this.questions = questions; 21 | this.answers = answers; 22 | this.nameservers = nameservers; 23 | this.additionalRecords = additionalRecords; 24 | } 25 | 26 | public static Builder builder(short id, short flags) { 27 | return new Builder(id, flags); 28 | } 29 | 30 | public static class Builder { 31 | 32 | private final short id, flags; 33 | private final List questions; 34 | private final List answers, nameservers, additionalRecords; 35 | 36 | Builder(short id, short flags) { 37 | this.id = id; 38 | this.flags = flags; 39 | questions = new ArrayList(); 40 | answers = new ArrayList(); 41 | nameservers = new ArrayList(); 42 | additionalRecords = new ArrayList(); 43 | } 44 | 45 | public void addQuestion(DNSQuery question) { 46 | questions.add(question); 47 | } 48 | 49 | public DNSResponse build() { 50 | return new DNSResponse(id, flags, questions, answers, nameservers, additionalRecords); 51 | } 52 | 53 | public void addAnswer(Record answer) { 54 | answers.add(answer); 55 | } 56 | 57 | public void addNameserver(Record nameserver) { 58 | nameservers.add(nameserver); 59 | } 60 | 61 | public void addAdditionalRecord(Record additionalRecord) { 62 | additionalRecords.add(additionalRecord); 63 | } 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | StringBuilder sb = new StringBuilder(512); 69 | sb.append(format("Transaction ID: %d\n", id())); 70 | sb.append(format("Flags: %d\n", flags())); 71 | sb.append(format("Questions: %s\n", questions())); 72 | 73 | sb.append(format("Answers: %s\n", answers())); 74 | sb.append(format("Authority: %s\n", nameservers())); 75 | sb.append(format("Additional: %s\n", additionalRecords())); 76 | return sb.toString(); 77 | } 78 | 79 | public List questions() { 80 | return questions; 81 | } 82 | 83 | public List answers() { 84 | return answers; 85 | } 86 | 87 | public List nameservers() { 88 | return nameservers; 89 | } 90 | 91 | public List additionalRecords() { 92 | return additionalRecords; 93 | } 94 | 95 | private int flags() { 96 | return flags; 97 | } 98 | 99 | public boolean isTruncated() { 100 | return ((flags & 64) == 64); 101 | } 102 | 103 | private int id() { 104 | return id; 105 | } 106 | } 107 | --------------------------------------------------------------------------------