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