DNSMessage.java

  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}