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 ({@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}