├── 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 |
--------------------------------------------------------------------------------