1package de.measite.minidns;
2
3import java.io.ByteArrayInputStream;
4import java.io.ByteArrayOutputStream;
5import java.io.DataInputStream;
6import java.io.DataOutputStream;
7import java.io.IOException;
8import java.util.Arrays;
9
10/**
11 * A DNS message as defined by rfc1035. The message consists of a header and
12 * 4 sections: question, answer, nameserver and addition resource record
13 * section.
14 * A message can either be parsed ({@link DNSMessage#parse(byte[])}) or serialized
15 * ({@link DNSMessage#toArray()}).
16 */
17public class DNSMessage {
18
19 /**
20 * Possible DNS reply codes.
21 */
22 public static enum RESPONSE_CODE {
23 NO_ERROR(0), FORMAT_ERR(1), SERVER_FAIL(2), NX_DOMAIN(3),
24 NO_IMP(4), REFUSED(5), YXDOMAIN(6), YXRRSET(7),
25 NXRRSET(8), NOT_AUTH(9),NOT_ZONE(10);
26
27 /**
28 * Reverse lookup table for response codes.
29 */
30 private final static RESPONSE_CODE INVERSE_LUT[] = new RESPONSE_CODE[]{
31 NO_ERROR, FORMAT_ERR, SERVER_FAIL, NX_DOMAIN, NO_IMP,
32 REFUSED, YXDOMAIN, YXRRSET, NXRRSET, NOT_AUTH, NOT_ZONE,
33 null, null, null, null, null
34 };
35
36 /**
37 * The response code value.
38 */
39 private final byte value;
40
41 /**
42 * Create a new response code.
43 * @param value The response code value.
44 */
45 private RESPONSE_CODE(int value) {
46 this.value = (byte)value;
47 }
48
49 /**
50 * Retrieve the byte value of the response code.
51 * @return the response code.
52 */
53 public byte getValue() {
54 return (byte) value;
55 }
56
57 /**
58 * Retrieve the response code for a byte value.
59 * @param value The byte value.
60 * @return The symbolic response code or null.
61 * @throws IllegalArgumentException if the value is not in the range of
62 * 0..15.
63 */
64 public static RESPONSE_CODE getResponseCode(int value) {
65 if (value < 0 || value > 15) {
66 throw new IllegalArgumentException();
67 }
68 return INVERSE_LUT[value];
69 }
70
71 };
72
73 /**
74 * Symbolic DNS Opcode values.
75 */
76 public static enum OPCODE {
77 QUERY(0),
78 INVERSE_QUERY(1),
79 STATUS(2),
80 NOTIFY(4),
81 UPDATE(5);
82
83 /**
84 * Lookup table for for obcode reolution.
85 */
86 private final static OPCODE INVERSE_LUT[] = new OPCODE[]{
87 QUERY, INVERSE_QUERY, STATUS, null, NOTIFY, UPDATE, null,
88 null, null, null, null, null, null, null, null
89 };
90
91 /**
92 * The value of this opcode.
93 */
94 private final byte value;
95
96 /**
97 * Create a new opcode for a given byte value.
98 * @param value The byte value of the opcode.
99 */
100 private OPCODE(int value) {
101 this.value = (byte)value;
102 }
103
104 /**
105 * Retrieve the byte value of this opcode.
106 * @return The byte value of this opcode.
107 */
108 public byte getValue() {
109 return value;
110 }
111
112 /**
113 * Retrieve the symbolic name of an opcode byte.
114 * @param value The byte value of the opcode.
115 * @return The symbolic opcode or null.
116 * @throws IllegalArgumentException If the byte value is not in the
117 * range 0..15.
118 */
119 public static OPCODE getOpcode(int value) {
120 if (value < 0 || value > 15) {
121 throw new IllegalArgumentException();
122 }
123 return INVERSE_LUT[value];
124 }
125
126 };
127
128 /**
129 * The DNS message id.
130 */
131 protected int id;
132
133 /**
134 * The DNS message opcode.
135 */
136 protected OPCODE opcode;
137
138 /**
139 * The response code of this dns message.
140 */
141 protected RESPONSE_CODE responseCode;
142
143 /**
144 * True if this is a query.
145 */
146 protected boolean query;
147
148 /**
149 * True if this is a authorative response.
150 */
151 protected boolean authoritativeAnswer;
152
153 /**
154 * True on truncate, tcp should be used.
155 */
156 protected boolean truncated;
157
158 /**
159 * True if the server should recurse.
160 */
161 protected boolean recursionDesired;
162
163 /**
164 * True if recursion is possible.
165 */
166 protected boolean recursionAvailable;
167
168 /**
169 * True if the server regarded the response as authentic.
170 */
171 protected boolean authenticData;
172
173 /**
174 * True if the server should not check the replies.
175 */
176 protected boolean checkDisabled;
177
178 /**
179 * The question section content.
180 */
181 protected Question questions[];
182
183 /**
184 * The answers section content.
185 */
186 protected Record answers[];
187
188 /**
189 * The nameserver records.
190 */
191 protected Record nameserverRecords[];
192
193 /**
194 * Additional resousrce records.
195 */
196 protected Record additionalResourceRecords[];
197
198 /**
199 * The receive timestamp of this message.
200 */
201 protected long receiveTimestamp;
202
203 /**
204 * Retrieve the current DNS message id.
205 * @return The current DNS message id.
206 */
207 public int getId() {
208 return id;
209 }
210
211 /**
212 * Set the current DNS message id.
213 * @param id The new DNS message id.
214 */
215 public void setId(int id) {
216 this.id = id & 0xffff;
217 }
218
219 /**
220 * Get the receive timestamp if this message was created via parse.
221 * This should be used to evaluate TTLs.
222 * @return The receive timestamp in milliseconds.
223 */
224 public long getReceiveTimestamp() {
225 return receiveTimestamp;
226 }
227
228 /**
229 * Retrieve the query type (true or false;
230 * @return True if this DNS message is a query.
231 */
232 public boolean isQuery() {
233 return query;
234 }
235
236 /**
237 * Set the query status of this message.
238 * @param query The new query status.
239 */
240 public void setQuery(boolean query) {
241 this.query = query;
242 }
243
244 /**
245 * True if the DNS message is an authoritative answer.
246 * @return True if this an authoritative DNS message.
247 */
248 public boolean isAuthoritativeAnswer() {
249 return authoritativeAnswer;
250 }
251
252 /**
253 * Set the authoritative answer flag.
254 * @param authoritativeAnswer Tge new authoritative answer value.
255 */
256 public void setAuthoritativeAnswer(boolean authoritativeAnswer) {
257 this.authoritativeAnswer = authoritativeAnswer;
258 }
259
260 /**
261 * Retrieve the truncation status of this message. True means that the
262 * client should try a tcp lookup.
263 * @return True if this message was truncated.
264 */
265 public boolean isTruncated() {
266 return truncated;
267 }
268
269 /**
270 * Set the truncation bit on this DNS message.
271 * @param truncated The new truncated bit status.
272 */
273 public void setTruncated(boolean truncated) {
274 this.truncated = truncated;
275 }
276
277 /**
278 * Check if this message preferes recursion.
279 * @return True if recursion is desired.
280 */
281 public boolean isRecursionDesired() {
282 return recursionDesired;
283 }
284
285 /**
286 * Set the recursion desired flag on this message.
287 * @param recursionDesired The new recusrion setting.
288 */
289 public void setRecursionDesired(boolean recursionDesired) {
290 this.recursionDesired = recursionDesired;
291 }
292
293 /**
294 * Retrieve the recursion available flag of this DNS message.
295 * @return The recursion available flag of this message.
296 */
297 public boolean isRecursionAvailable() {
298 return recursionAvailable;
299 }
300
301 /**
302 * Set the recursion available flog from this DNS message.
303 * @param recursionAvailable The new recursion available status.
304 */
305 public void setRecursionAvailable(boolean recursionAvailable) {
306 this.recursionAvailable = recursionAvailable;
307 }
308
309 /**
310 * Retrieve the authentic data flag of this message.
311 * @return The authentic data flag.
312 */
313 public boolean isAuthenticData() {
314 return authenticData;
315 }
316
317 /**
318 * Set the authentic data flag on this DNS message.
319 * @param authenticData The new authentic data flag value.
320 */
321 public void setAuthenticData(boolean authenticData) {
322 this.authenticData = authenticData;
323 }
324
325 /**
326 * Check if checks are disabled.
327 * @return The status of the CheckDisabled flag.
328 */
329 public boolean isCheckDisabled() {
330 return checkDisabled;
331 }
332
333 /**
334 * Change the check status of this packet.
335 * @param checkDisabled The new check disabled value.
336 */
337 public void setCheckDisabled(boolean checkDisabled) {
338 this.checkDisabled = checkDisabled;
339 }
340
341 /**
342 * Generate a binary dns packet out of this message.
343 * @return byte[] the binary representation.
344 * @throws IOException Should never happen.
345 */
346 public byte[] toArray() throws IOException {
347 ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
348 DataOutputStream dos = new DataOutputStream(baos);
349 int header = 0;
350 if (query) {
351 header += 1 << 15;
352 }
353 if (opcode != null) {
354 header += opcode.getValue() << 11;
355 }
356 if (authoritativeAnswer) {
357 header += 1 << 10;
358 }
359 if (truncated) {
360 header += 1 << 9;
361 }
362 if (recursionDesired) {
363 header += 1 << 8;
364 }
365 if (recursionAvailable) {
366 header += 1 << 7;
367 }
368 if (authenticData) {
369 header += 1 << 5;
370 }
371 if (checkDisabled) {
372 header += 1 << 4;
373 }
374 if (responseCode != null) {
375 header += responseCode.getValue();
376 }
377 dos.writeShort((short)id);
378 dos.writeShort((short)header);
379 if (questions == null) {
380 dos.writeShort(0);
381 } else {
382 dos.writeShort((short)questions.length);
383 }
384 if (answers == null) {
385 dos.writeShort(0);
386 } else {
387 dos.writeShort((short)answers.length);
388 }
389 if (nameserverRecords == null) {
390 dos.writeShort(0);
391 } else {
392 dos.writeShort((short)nameserverRecords.length);
393 }
394 if (additionalResourceRecords == null) {
395 dos.writeShort(0);
396 } else {
397 dos.writeShort((short)additionalResourceRecords.length);
398 }
399 for (Question question: questions) {
400 dos.write(question.toByteArray());
401 }
402 dos.flush();
403 return baos.toByteArray();
404 }
405
406 /**
407 * Build a DNS Message based on a binary DNS message.
408 * @param data The DNS message data.
409 * @return Parsed DNSMessage message.
410 * @throws IOException On read errors.
411 */
412 public static DNSMessage parse(byte data[]) throws IOException {
413 ByteArrayInputStream bis = new ByteArrayInputStream(data);
414 DataInputStream dis = new DataInputStream(bis);
415 DNSMessage message = new DNSMessage();
416 message.id = dis.readUnsignedShort();
417 int header = dis.readUnsignedShort();
418 message.query = ((header >> 15) & 1) == 0;
419 message.opcode = OPCODE.getOpcode((header >> 11) & 0xf);
420 message.authoritativeAnswer = ((header >> 10) & 1) == 1;
421 message.truncated = ((header >> 9) & 1) == 1;
422 message.recursionDesired = ((header >> 8) & 1) == 1;
423 message.recursionAvailable = ((header >> 7) & 1) == 1;
424 message.authenticData = ((header >> 5) & 1) == 1;
425 message.checkDisabled = ((header >> 4) & 1) == 1;
426 message.responseCode = RESPONSE_CODE.getResponseCode(header & 0xf);
427 message.receiveTimestamp = System.currentTimeMillis();
428 int questionCount = dis.readUnsignedShort();
429 int answerCount = dis.readUnsignedShort();
430 int nameserverCount = dis.readUnsignedShort();
431 int additionalResourceRecordCount = dis.readUnsignedShort();
432 message.questions = new Question[questionCount];
433 while (questionCount-- > 0) {
434 Question q = Question.parse(dis, data);
435 message.questions[questionCount] = q;
436 }
437 message.answers = new Record[answerCount];
438 while (answerCount-- > 0) {
439 Record rr = new Record();
440 rr.parse(dis, data);
441 message.answers[answerCount] = rr;
442 }
443 message.nameserverRecords = new Record[nameserverCount];
444 while (nameserverCount-- > 0) {
445 Record rr = new Record();
446 rr.parse(dis, data);
447 message.nameserverRecords[nameserverCount] = rr;
448 }
449 message.additionalResourceRecords =
450 new Record[additionalResourceRecordCount];
451 while (additionalResourceRecordCount-- > 0) {
452 Record rr = new Record();
453 rr.parse(dis, data);
454 message.additionalResourceRecords[additionalResourceRecordCount] =
455 rr;
456 }
457 return message;
458 }
459
460 /**
461 * Set the question part of this message.
462 * @param questions The questions.
463 */
464 public void setQuestions(Question ... questions) {
465 this.questions = questions;
466 }
467
468 /**
469 * Retrieve the opcode of this message.
470 * @return The opcode of this message.
471 */
472 public OPCODE getOpcode() {
473 return opcode;
474 }
475
476 /**
477 * Retrieve the response code of this message.
478 * @return The response code.
479 */
480 public RESPONSE_CODE getResponseCode() {
481 return responseCode;
482 }
483
484 /**
485 * Retrieve the question section of this message.
486 * @return The DNS question section.
487 */
488 public Question[] getQuestions() {
489 return questions;
490 }
491
492 /**
493 * Retrieve the answer records of this DNS message.
494 * @return The answer section of this DNS message.
495 */
496 public Record[] getAnswers() {
497 return answers;
498 }
499
500 /**
501 * Retrieve the nameserver records of this DNS message.
502 * @return The nameserver section of this DNS message.
503 */
504 public Record[] getNameserverRecords() {
505 return nameserverRecords;
506 }
507
508 /**
509 * Retrieve the additional resource records attached to this DNS message.
510 * @return The additional resource record section of this DNS message.
511 */
512 public Record[] getAdditionalResourceRecords() {
513 return additionalResourceRecords;
514 }
515
516 public String toString() {
517 return "-- DNSMessage " + id + " --\n" +
518 "Q" + Arrays.toString(questions) +
519 "NS" + Arrays.toString(nameserverRecords) +
520 "A" + Arrays.toString(answers) +
521 "ARR" + Arrays.toString(additionalResourceRecords);
522 }
523
524}