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 ({@see #parse(byte[])}) or serialized
15 * ({@see #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
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 * Retrieve the current DNS message id.
200 * @return The current DNS message id.
201 */
202 public int getId() {
203 return id;
204 }
205
206 /**
207 * Set the current DNS message id.
208 * @param id The new DNS message id.
209 */
210 public void setId(int id) {
211 this.id = id & 0xffff;
212 }
213
214 /**
215 * Retrieve the query type (true or false;
216 * @return True if this DNS message is a query.
217 */
218 public boolean isQuery() {
219 return query;
220 }
221
222 /**
223 * Set the query status of this message.
224 * @param query The new query status.
225 */
226 public void setQuery(boolean query) {
227 this.query = query;
228 }
229
230 /**
231 * True if the DNS message is an authoritative answer.
232 * @return True if this an authoritative DNS message.
233 */
234 public boolean isAuthoritativeAnswer() {
235 return authoritativeAnswer;
236 }
237
238 /**
239 * Set the authoritative answer flag.
240 * @param authoritativeAnswer Tge new authoritative answer value.
241 */
242 public void setAuthoritativeAnswer(boolean authoritativeAnswer) {
243 this.authoritativeAnswer = authoritativeAnswer;
244 }
245
246 /**
247 * Retrieve the truncation status of this message. True means that the
248 * client should try a tcp lookup.
249 * @return True if this message was truncated.
250 */
251 public boolean isTruncated() {
252 return truncated;
253 }
254
255 /**
256 * Set the truncation bit on this DNS message.
257 * @param truncated The new truncated bit status.
258 */
259 public void setTruncated(boolean truncated) {
260 this.truncated = truncated;
261 }
262
263 /**
264 * Check if this message preferes recursion.
265 * @return True if recursion is desired.
266 */
267 public boolean isRecursionDesired() {
268 return recursionDesired;
269 }
270
271 /**
272 * Set the recursion desired flag on this message.
273 * @param recursionDesired The new recusrion setting.
274 */
275 public void setRecursionDesired(boolean recursionDesired) {
276 this.recursionDesired = recursionDesired;
277 }
278
279 /**
280 * Retrieve the recursion available flag of this DNS message.
281 * @return The recursion available flag of this message.
282 */
283 public boolean isRecursionAvailable() {
284 return recursionAvailable;
285 }
286
287 /**
288 * Set the recursion available flog from this DNS message.
289 * @param recursionAvailable The new recursion available status.
290 */
291 public void setRecursionAvailable(boolean recursionAvailable) {
292 this.recursionAvailable = recursionAvailable;
293 }
294
295 /**
296 * Retrieve the authentic data flag of this message.
297 * @return The authentic data flag.
298 */
299 public boolean isAuthenticData() {
300 return authenticData;
301 }
302
303 /**
304 * Set the authentic data flag on this DNS message.
305 * @param authenticData The new authentic data flag value.
306 */
307 public void setAuthenticData(boolean authenticData) {
308 this.authenticData = authenticData;
309 }
310
311 /**
312 * Check if checks are disabled.
313 * @return The status of the CheckDisabled flag.
314 */
315 public boolean isCheckDisabled() {
316 return checkDisabled;
317 }
318
319 /**
320 * Change the check status of this packet.
321 * @param checkDisabled
322 */
323 public void setCheckDisabled(boolean checkDisabled) {
324 this.checkDisabled = checkDisabled;
325 }
326
327 /**
328 * Generate a binary dns packet out of this message.
329 * @return byte[] the binary representation.
330 * @throws IOException Should never happen.
331 */
332 public byte[] toArray() throws IOException {
333 ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
334 DataOutputStream dos = new DataOutputStream(baos);
335 int header = 0;
336 if (query) {
337 header += 1 << 15;
338 }
339 if (opcode != null) {
340 header += opcode.getValue() << 11;
341 }
342 if (authoritativeAnswer) {
343 header += 1 << 10;
344 }
345 if (truncated) {
346 header += 1 << 9;
347 }
348 if (recursionDesired) {
349 header += 1 << 8;
350 }
351 if (recursionAvailable) {
352 header += 1 << 7;
353 }
354 if (authenticData) {
355 header += 1 << 5;
356 }
357 if (checkDisabled) {
358 header += 1 << 4;
359 }
360 if (responseCode != null) {
361 header += responseCode.getValue();
362 }
363 dos.writeShort((short)id);
364 dos.writeShort((short)header);
365 if (questions == null) {
366 dos.writeShort(0);
367 } else {
368 dos.writeShort((short)questions.length);
369 }
370 if (answers == null) {
371 dos.writeShort(0);
372 } else {
373 dos.writeShort((short)answers.length);
374 }
375 if (nameserverRecords == null) {
376 dos.writeShort(0);
377 } else {
378 dos.writeShort((short)nameserverRecords.length);
379 }
380 if (additionalResourceRecords == null) {
381 dos.writeShort(0);
382 } else {
383 dos.writeShort((short)additionalResourceRecords.length);
384 }
385 for (Question question: questions) {
386 dos.write(question.toByteArray());
387 }
388 dos.flush();
389 return baos.toByteArray();
390 }
391
392 /**
393 * Build a DNS Message based on a binary DNS message.
394 * @param data The DNS message data.
395 * @return Parsed DNSMessage message.
396 * @throws IOException On read errors.
397 */
398 public static DNSMessage parse(byte data[]) throws IOException {
399 ByteArrayInputStream bis = new ByteArrayInputStream(data);
400 DataInputStream dis = new DataInputStream(bis);
401 DNSMessage message = new DNSMessage();
402 message.id = dis.readUnsignedShort();
403 int header = dis.readUnsignedShort();
404 message.query = ((header >> 15) & 1) == 0;
405 message.opcode = OPCODE.getOpcode((header >> 11) & 0xf);
406 message.authoritativeAnswer = ((header >> 10) & 1) == 1;
407 message.truncated = ((header >> 9) & 1) == 1;
408 message.recursionDesired = ((header >> 8) & 1) == 1;
409 message.recursionAvailable = ((header >> 7) & 1) == 1;
410 message.authenticData = ((header >> 5) & 1) == 1;
411 message.checkDisabled = ((header >> 4) & 1) == 1;
412 message.responseCode = RESPONSE_CODE.getResponseCode(header & 0xf);
413 int questionCount = dis.readUnsignedShort();
414 int answerCount = dis.readUnsignedShort();
415 int nameserverCount = dis.readUnsignedShort();
416 int additionalResourceRecordCount = dis.readUnsignedShort();
417 message.questions = new Question[questionCount];
418 while (questionCount-- > 0) {
419 Question q = Question.parse(dis, data);
420 message.questions[questionCount] = q;
421 }
422 message.answers = new Record[answerCount];
423 while (answerCount-- > 0) {
424 Record rr = new Record();
425 rr.parse(dis, data);
426 message.answers[answerCount] = rr;
427 }
428 message.nameserverRecords = new Record[nameserverCount];
429 while (nameserverCount-- > 0) {
430 Record rr = new Record();
431 rr.parse(dis, data);
432 message.nameserverRecords[nameserverCount] = rr;
433 }
434 message.additionalResourceRecords =
435 new Record[additionalResourceRecordCount];
436 while (additionalResourceRecordCount-- > 0) {
437 Record rr = new Record();
438 rr.parse(dis, data);
439 message.additionalResourceRecords[additionalResourceRecordCount] =
440 rr;
441 }
442 return message;
443 }
444
445 /**
446 * Set the question part of this message.
447 * @param questions The questions.
448 */
449 public void setQuestions(Question ... questions) {
450 this.questions = questions;
451 }
452
453 /**
454 * Retrieve the opcode of this message.
455 * @return The opcode of this message.
456 */
457 public OPCODE getOpcode() {
458 return opcode;
459 }
460
461 /**
462 * Retrieve the response code of this message.
463 * @return The response code.
464 */
465 public RESPONSE_CODE getResponseCode() {
466 return responseCode;
467 }
468
469 /**
470 * Retrieve the question section of this message.
471 * @return The DNS question section.
472 */
473 public Question[] getQuestions() {
474 return questions;
475 }
476
477 /**
478 * Retrieve the answer records of this DNS message.
479 * @return The answer section of this DNS message.
480 */
481 public Record[] getAnswers() {
482 return answers;
483 }
484
485 /**
486 * Retrieve the nameserver records of this DNS message.
487 * @return The nameserver section of this DNS message.
488 */
489 public Record[] getNameserverRecords() {
490 return nameserverRecords;
491 }
492
493 /**
494 * Retrieve the additional resource records attached to this DNS message.
495 * @return The additional resource record section of this DNS message.
496 */
497 public Record[] getAdditionalResourceRecords() {
498 return additionalResourceRecords;
499 }
500
501 public String toString() {
502 return "-- DNSMessage " + id + " --\n" +
503 Arrays.toString(answers);
504 }
505
506}