001/* 002 * Copyright 2015-2024 the original author or authors 003 * 004 * This software is licensed under the Apache License, Version 2.0, 005 * the GNU Lesser General Public License version 2 or later ("LGPL") 006 * and the WTFPL. 007 * You may choose either license to govern your use of this software only 008 * upon the condition that you accept all of the terms of either 009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. 010 */ 011package org.minidns.dnsmessage; 012 013import org.minidns.edns.Edns; 014import org.minidns.record.Data; 015import org.minidns.record.OPT; 016import org.minidns.record.Record; 017import org.minidns.record.Record.TYPE; 018 019import java.io.ByteArrayInputStream; 020import java.io.ByteArrayOutputStream; 021import java.io.DataInputStream; 022import java.io.DataOutputStream; 023import java.io.IOException; 024import java.io.OutputStream; 025import java.net.DatagramPacket; 026import java.net.InetAddress; 027import java.nio.ByteBuffer; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Collection; 031import java.util.Collections; 032import java.util.Date; 033import java.util.HashMap; 034import java.util.HashSet; 035import java.util.List; 036import java.util.Map; 037import java.util.Set; 038import java.util.logging.Level; 039import java.util.logging.Logger; 040 041/** 042 * A DNS message as defined by RFC 1035. The message consists of a header and 043 * 4 sections: question, answer, nameserver and addition resource record 044 * section. 045 * A message can either be parsed ({@link #DnsMessage(byte[])}) or serialized 046 * ({@link DnsMessage#toArray()}). 047 * 048 * @see <a href="https://www.ietf.org/rfc/rfc1035.txt">RFC 1035</a> 049 */ 050public class DnsMessage { 051 052 private static final Logger LOGGER = Logger.getLogger(DnsMessage.class.getName()); 053 054 /** 055 * Possible DNS response codes. 056 * 057 * @see <a href= 058 * "http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6"> 059 * IANA Domain Name System (DNS) Paramters - DNS RCODEs</a> 060 * @see <a href="http://tools.ietf.org/html/rfc6895#section-2.3">RFC 6895 § 2.3</a> 061 */ 062 public enum RESPONSE_CODE { 063 NO_ERROR(0), 064 FORMAT_ERR(1), 065 SERVER_FAIL(2), 066 NX_DOMAIN(3), 067 NO_IMP(4), 068 REFUSED(5), 069 YXDOMAIN(6), 070 YXRRSET(7), 071 NXRRSET(8), 072 NOT_AUTH(9), 073 NOT_ZONE(10), 074 BADVERS_BADSIG(16), 075 BADKEY(17), 076 BADTIME(18), 077 BADMODE(19), 078 BADNAME(20), 079 BADALG(21), 080 BADTRUNC(22), 081 BADCOOKIE(23), 082 ; 083 084 /** 085 * Reverse lookup table for response codes. 086 */ 087 private static final Map<Integer, RESPONSE_CODE> INVERSE_LUT = new HashMap<>(RESPONSE_CODE.values().length); 088 089 static { 090 for (RESPONSE_CODE responseCode : RESPONSE_CODE.values()) { 091 INVERSE_LUT.put((int) responseCode.value, responseCode); 092 } 093 } 094 095 /** 096 * The response code value. 097 */ 098 private final byte value; 099 100 /** 101 * Create a new response code. 102 * 103 * @param value The response code value. 104 */ 105 RESPONSE_CODE(int value) { 106 this.value = (byte) value; 107 } 108 109 /** 110 * Retrieve the byte value of the response code. 111 * 112 * @return the response code. 113 */ 114 public byte getValue() { 115 return value; 116 } 117 118 /** 119 * Retrieve the response code for a byte value. 120 * 121 * @param value The byte value. 122 * @return The symbolic response code or null. 123 * @throws IllegalArgumentException if the value is not in the range of 0..15. 124 */ 125 public static RESPONSE_CODE getResponseCode(int value) throws IllegalArgumentException { 126 if (value < 0 || value > 65535) { 127 throw new IllegalArgumentException(); 128 } 129 return INVERSE_LUT.get(value); 130 } 131 132 } 133 134 /** 135 * Symbolic DNS Opcode values. 136 * 137 * @see <a href= 138 * "http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5"> 139 * IANA Domain Name System (DNS) Paramters - DNS OpCodes</a> 140 */ 141 public enum OPCODE { 142 QUERY, 143 INVERSE_QUERY, 144 STATUS, 145 UNASSIGNED3, 146 NOTIFY, 147 UPDATE, 148 ; 149 150 /** 151 * Lookup table for for opcode resolution. 152 */ 153 private static final OPCODE[] INVERSE_LUT = new OPCODE[OPCODE.values().length]; 154 155 static { 156 for (OPCODE opcode : OPCODE.values()) { 157 if (INVERSE_LUT[opcode.getValue()] != null) { 158 throw new IllegalStateException(); 159 } 160 INVERSE_LUT[opcode.getValue()] = opcode; 161 } 162 } 163 164 /** 165 * The value of this opcode. 166 */ 167 private final byte value; 168 169 /** 170 * Create a new opcode for a given byte value. 171 * 172 */ 173 @SuppressWarnings("EnumOrdinal") 174 OPCODE() { 175 this.value = (byte) this.ordinal(); 176 } 177 178 /** 179 * Retrieve the byte value of this opcode. 180 * 181 * @return The byte value of this opcode. 182 */ 183 public byte getValue() { 184 return value; 185 } 186 187 /** 188 * Retrieve the symbolic name of an opcode byte. 189 * 190 * @param value The byte value of the opcode. 191 * @return The symbolic opcode or null. 192 * @throws IllegalArgumentException If the byte value is not in the 193 * range 0..15. 194 */ 195 public static OPCODE getOpcode(int value) throws IllegalArgumentException { 196 if (value < 0 || value > 15) { 197 throw new IllegalArgumentException(); 198 } 199 if (value >= INVERSE_LUT.length) { 200 return null; 201 } 202 return INVERSE_LUT[value]; 203 } 204 205 } 206 207 /** 208 * The DNS message id. 209 */ 210 public final int id; 211 212 /** 213 * The DNS message opcode. 214 */ 215 public final OPCODE opcode; 216 217 /** 218 * The response code of this dns message. 219 */ 220 public final RESPONSE_CODE responseCode; 221 222 /** 223 * The QR flag of the DNS message header. Note that this will be <code>true</code> if the message is a 224 * <b>response</b> and <code>false</code> if it is a <b>query</b>. 225 * 226 * @see <a href="https://www.ietf.org/rfc/rfc1035.txt">RFC 1035 § 4.1.1</a> 227 */ 228 public final boolean qr; 229 230 /** 231 * True if this is a authorative response. If set, the responding nameserver is an authority for the domain name in 232 * the question section. Note that the answer section may have multiple owner names because of aliases. This flag 233 * corresponds to the name which matches the query name, or the first owner name in the query section. 234 * 235 * @see <a href="https://www.ietf.org/rfc/rfc1035.txt">RFC 1035 § 4.1.1. Header section format</a> 236 */ 237 public final boolean authoritativeAnswer; 238 239 /** 240 * True if message is truncated. Then TCP should be used. 241 */ 242 public final boolean truncated; 243 244 /** 245 * True if the server should recurse. 246 */ 247 public final boolean recursionDesired; 248 249 /** 250 * True if recursion is possible. 251 */ 252 public final boolean recursionAvailable; 253 254 /** 255 * True if the server regarded the response as authentic. 256 */ 257 public final boolean authenticData; 258 259 /** 260 * True if the server should not perform DNSSEC validation before returning the result. 261 */ 262 public final boolean checkingDisabled; 263 264 /** 265 * The question section content. Usually there will be only one question. 266 * <p> 267 * This list is unmodifiable. 268 * </p> 269 */ 270 public final List<Question> questions; 271 272 /** 273 * The answers section records. Note that it is not guaranteed that all records found in this section will be direct 274 * answers to the question in the query. If DNSSEC is used, then this section also contains the RRSIG record. 275 * <p> 276 * This list is unmodifiable. 277 * </p> 278 */ 279 public final List<Record<? extends Data>> answerSection; 280 281 /** 282 * The Authority Section. Note that it is not guaranteed that this section only contains nameserver records. If DNSSEC is used, then this section could also contain a NSEC(3) record. 283 * <p> 284 * This list is unmodifiable. 285 * </p> 286 */ 287 public final List<Record<? extends Data>> authoritySection; 288 289 /** 290 * The additional section. It eventually contains RRs which relate to the query. 291 * <p> 292 * This list is unmodifiable. 293 * </p> 294 */ 295 public final List<Record<? extends Data>> additionalSection; 296 297 public final int optRrPosition; 298 299 /** 300 * The optional but very common EDNS information. Note that this field is lazily populated. 301 * 302 */ 303 private Edns edns; 304 305 /** 306 * The receive timestamp. Set only if this message was created via parse. 307 * This should be used to evaluate TTLs. 308 */ 309 public final long receiveTimestamp; 310 311 protected DnsMessage(Builder builder) { 312 this.id = builder.id; 313 this.opcode = builder.opcode; 314 this.responseCode = builder.responseCode; 315 this.receiveTimestamp = builder.receiveTimestamp; 316 this.qr = builder.query; 317 this.authoritativeAnswer = builder.authoritativeAnswer; 318 this.truncated = builder.truncated; 319 this.recursionDesired = builder.recursionDesired; 320 this.recursionAvailable = builder.recursionAvailable; 321 this.authenticData = builder.authenticData; 322 this.checkingDisabled = builder.checkingDisabled; 323 324 if (builder.questions == null) { 325 this.questions = Collections.emptyList(); 326 } else { 327 List<Question> q = new ArrayList<>(builder.questions.size()); 328 q.addAll(builder.questions); 329 this.questions = Collections.unmodifiableList(q); 330 } 331 332 if (builder.answerSection == null) { 333 this.answerSection = Collections.emptyList(); 334 } else { 335 List<Record<? extends Data>> a = new ArrayList<>(builder.answerSection.size()); 336 a.addAll(builder.answerSection); 337 this.answerSection = Collections.unmodifiableList(a); 338 } 339 340 if (builder.authoritySection == null) { 341 this.authoritySection = Collections.emptyList(); 342 } else { 343 List<Record<? extends Data>> n = new ArrayList<>(builder.authoritySection.size()); 344 n.addAll(builder.authoritySection); 345 this.authoritySection = Collections.unmodifiableList(n); 346 } 347 348 if (builder.additionalSection == null && builder.ednsBuilder == null) { 349 this.additionalSection = Collections.emptyList(); 350 } else { 351 int size = 0; 352 if (builder.additionalSection != null) { 353 size += builder.additionalSection.size(); 354 } 355 if (builder.ednsBuilder != null) { 356 size++; 357 } 358 List<Record<? extends Data>> a = new ArrayList<>(size); 359 if (builder.additionalSection != null) { 360 a.addAll(builder.additionalSection); 361 } 362 if (builder.ednsBuilder != null) { 363 Edns edns = builder.ednsBuilder.build(); 364 this.edns = edns; 365 a.add(edns.asRecord()); 366 } 367 this.additionalSection = Collections.unmodifiableList(a); 368 } 369 370 optRrPosition = getOptRrPosition(this.additionalSection); 371 372 if (optRrPosition != -1) { 373 // Verify that there are no further OPT records but the one we already found. 374 for (int i = optRrPosition + 1; i < this.additionalSection.size(); i++) { 375 if (this.additionalSection.get(i).type == TYPE.OPT) { 376 throw new IllegalArgumentException("There must be only one OPT pseudo RR in the additional section"); 377 } 378 } 379 } 380 381 // TODO Add verification of dns message state here 382 } 383 384 /** 385 * Build a DNS Message based on a binary DNS message. 386 * 387 * @param data The DNS message data. 388 * @throws IOException On read errors. 389 */ 390 public DnsMessage(byte[] data) throws IOException { 391 ByteArrayInputStream bis = new ByteArrayInputStream(data); 392 DataInputStream dis = new DataInputStream(bis); 393 id = dis.readUnsignedShort(); 394 int header = dis.readUnsignedShort(); 395 qr = ((header >> 15) & 1) == 1; 396 opcode = OPCODE.getOpcode((header >> 11) & 0xf); 397 authoritativeAnswer = ((header >> 10) & 1) == 1; 398 truncated = ((header >> 9) & 1) == 1; 399 recursionDesired = ((header >> 8) & 1) == 1; 400 recursionAvailable = ((header >> 7) & 1) == 1; 401 authenticData = ((header >> 5) & 1) == 1; 402 checkingDisabled = ((header >> 4) & 1) == 1; 403 responseCode = RESPONSE_CODE.getResponseCode(header & 0xf); 404 receiveTimestamp = System.currentTimeMillis(); 405 int questionCount = dis.readUnsignedShort(); 406 int answerCount = dis.readUnsignedShort(); 407 int nameserverCount = dis.readUnsignedShort(); 408 int additionalResourceRecordCount = dis.readUnsignedShort(); 409 questions = new ArrayList<>(questionCount); 410 for (int i = 0; i < questionCount; i++) { 411 questions.add(new Question(dis, data)); 412 } 413 answerSection = new ArrayList<>(answerCount); 414 for (int i = 0; i < answerCount; i++) { 415 answerSection.add(Record.parse(dis, data)); 416 } 417 authoritySection = new ArrayList<>(nameserverCount); 418 for (int i = 0; i < nameserverCount; i++) { 419 authoritySection.add(Record.parse(dis, data)); 420 } 421 additionalSection = new ArrayList<>(additionalResourceRecordCount); 422 for (int i = 0; i < additionalResourceRecordCount; i++) { 423 additionalSection.add(Record.parse(dis, data)); 424 } 425 optRrPosition = getOptRrPosition(additionalSection); 426 } 427 428 /** 429 * Constructs an normalized version of the given DnsMessage by setting the id to '0'. 430 * 431 * @param message the message of which normalized version should be constructed. 432 */ 433 private DnsMessage(DnsMessage message) { 434 id = 0; 435 qr = message.qr; 436 opcode = message.opcode; 437 authoritativeAnswer = message.authoritativeAnswer; 438 truncated = message.truncated; 439 recursionDesired = message.recursionDesired; 440 recursionAvailable = message.recursionAvailable; 441 authenticData = message.authenticData; 442 checkingDisabled = message.checkingDisabled; 443 responseCode = message.responseCode; 444 receiveTimestamp = message.receiveTimestamp; 445 questions = message.questions; 446 answerSection = message.answerSection; 447 authoritySection = message.authoritySection; 448 additionalSection = message.additionalSection; 449 optRrPosition = message.optRrPosition; 450 } 451 452 private static int getOptRrPosition(List<Record<? extends Data>> additionalSection) { 453 int optRrPosition = -1; 454 for (int i = 0; i < additionalSection.size(); i++) { 455 Record<? extends Data> record = additionalSection.get(i); 456 if (record.type == Record.TYPE.OPT) { 457 optRrPosition = i; 458 break; 459 } 460 } 461 return optRrPosition; 462 } 463 464 /** 465 * Generate a binary dns packet out of this message. 466 * 467 * @return byte[] the binary representation. 468 */ 469 public byte[] toArray() { 470 return serialize().clone(); 471 } 472 473 public DatagramPacket asDatagram(InetAddress address, int port) { 474 byte[] bytes = serialize(); 475 return new DatagramPacket(bytes, bytes.length, address, port); 476 } 477 478 public void writeTo(OutputStream outputStream) throws IOException { 479 writeTo(outputStream, true); 480 } 481 482 public void writeTo(OutputStream outputStream, boolean writeLength) throws IOException { 483 byte[] bytes = serialize(); 484 DataOutputStream dataOutputStream = new DataOutputStream(outputStream); 485 if (writeLength) { 486 dataOutputStream.writeShort(bytes.length); 487 } 488 dataOutputStream.write(bytes); 489 } 490 491 public ByteBuffer getInByteBuffer() { 492 byte[] bytes = serialize().clone(); 493 return ByteBuffer.wrap(bytes); 494 } 495 496 private byte[] byteCache; 497 498 private byte[] serialize() { 499 if (byteCache != null) { 500 return byteCache; 501 } 502 503 ByteArrayOutputStream baos = new ByteArrayOutputStream(512); 504 DataOutputStream dos = new DataOutputStream(baos); 505 int header = calculateHeaderBitmap(); 506 try { 507 dos.writeShort((short) id); 508 dos.writeShort((short) header); 509 if (questions == null) { 510 dos.writeShort(0); 511 } else { 512 dos.writeShort((short) questions.size()); 513 } 514 if (answerSection == null) { 515 dos.writeShort(0); 516 } else { 517 dos.writeShort((short) answerSection.size()); 518 } 519 if (authoritySection == null) { 520 dos.writeShort(0); 521 } else { 522 dos.writeShort((short) authoritySection.size()); 523 } 524 if (additionalSection == null) { 525 dos.writeShort(0); 526 } else { 527 dos.writeShort((short) additionalSection.size()); 528 } 529 if (questions != null) { 530 for (Question question : questions) { 531 dos.write(question.toByteArray()); 532 } 533 } 534 if (answerSection != null) { 535 for (Record<? extends Data> answer : answerSection) { 536 dos.write(answer.toByteArray()); 537 } 538 } 539 if (authoritySection != null) { 540 for (Record<? extends Data> nameserverRecord : authoritySection) { 541 dos.write(nameserverRecord.toByteArray()); 542 } 543 } 544 if (additionalSection != null) { 545 for (Record<? extends Data> additionalResourceRecord : additionalSection) { 546 dos.write(additionalResourceRecord.toByteArray()); 547 } 548 } 549 dos.flush(); 550 } catch (IOException e) { 551 // Should never happen. 552 throw new AssertionError(e); 553 } 554 byteCache = baos.toByteArray(); 555 return byteCache; 556 } 557 558 int calculateHeaderBitmap() { 559 int header = 0; 560 if (qr) { 561 header += 1 << 15; 562 } 563 if (opcode != null) { 564 header += opcode.getValue() << 11; 565 } 566 if (authoritativeAnswer) { 567 header += 1 << 10; 568 } 569 if (truncated) { 570 header += 1 << 9; 571 } 572 if (recursionDesired) { 573 header += 1 << 8; 574 } 575 if (recursionAvailable) { 576 header += 1 << 7; 577 } 578 if (authenticData) { 579 header += 1 << 5; 580 } 581 if (checkingDisabled) { 582 header += 1 << 4; 583 } 584 if (responseCode != null) { 585 header += responseCode.getValue(); 586 } 587 return header; 588 } 589 590 public Question getQuestion() { 591 return questions.get(0); 592 } 593 594 /** 595 * Copy the questions found in the question section. 596 * 597 * @return a copy of the question section questions. 598 * @see #questions 599 */ 600 public List<Question> copyQuestions() { 601 List<Question> copy = new ArrayList<>(questions.size()); 602 copy.addAll(questions); 603 return copy; 604 } 605 606 /** 607 * Copy the records found in the answer section into a new list. 608 * 609 * @return a copy of the answer section records. 610 * @see #answerSection 611 */ 612 public List<Record<? extends Data>> copyAnswers() { 613 List<Record<? extends Data>> res = new ArrayList<>(answerSection.size()); 614 res.addAll(answerSection); 615 return res; 616 } 617 618 /** 619 * Copy the records found in the authority section into a new list. 620 * 621 * @return a copy of the authority section records. 622 * @see #authoritySection 623 */ 624 public List<Record<? extends Data>> copyAuthority() { 625 List<Record<? extends Data>> res = new ArrayList<>(authoritySection.size()); 626 res.addAll(authoritySection); 627 return res; 628 } 629 630 public Edns getEdns() { 631 if (edns != null) return edns; 632 633 Record<OPT> optRecord = getOptPseudoRecord(); 634 if (optRecord == null) return null; 635 edns = new Edns(optRecord); 636 return edns; 637 } 638 639 @SuppressWarnings("unchecked") 640 public Record<OPT> getOptPseudoRecord() { 641 if (optRrPosition == -1) return null; 642 return (Record<OPT>) additionalSection.get(optRrPosition); 643 } 644 645 /** 646 * Check if the EDNS DO (DNSSEC OK) flag is set. 647 * 648 * @return true if the DO flag is set. 649 */ 650 public boolean isDnssecOk() { 651 Edns edns = getEdns(); 652 if (edns == null) 653 return false; 654 655 return edns.dnssecOk; 656 } 657 658 private String toStringCache; 659 660 @Override 661 public String toString() { 662 if (toStringCache != null) return toStringCache; 663 664 StringBuilder sb = new StringBuilder("DnsMessage"); 665 asBuilder().writeToStringBuilder(sb); 666 667 toStringCache = sb.toString(); 668 return toStringCache; 669 } 670 671 private String terminalOutputCache; 672 673 /** 674 * Format the DnsMessage object in a way suitable for terminal output. 675 * The format is loosely based on the output provided by {@code dig}. 676 * 677 * @return This message as a String suitable for terminal output. 678 */ 679 @SuppressWarnings("JavaUtilDate") 680 public String asTerminalOutput() { 681 if (terminalOutputCache != null) return terminalOutputCache; 682 683 StringBuilder sb = new StringBuilder(";; ->>HEADER<<-") 684 .append(" opcode: ").append(opcode) 685 .append(", status: ").append(responseCode) 686 .append(", id: ").append(id).append("\n") 687 .append(";; flags:"); 688 if (!qr) sb.append(" qr"); 689 if (authoritativeAnswer) sb.append(" aa"); 690 if (truncated) sb.append(" tr"); 691 if (recursionDesired) sb.append(" rd"); 692 if (recursionAvailable) sb.append(" ra"); 693 if (authenticData) sb.append(" ad"); 694 if (checkingDisabled) sb.append(" cd"); 695 sb.append("; QUERY: ").append(questions.size()) 696 .append(", ANSWER: ").append(answerSection.size()) 697 .append(", AUTHORITY: ").append(authoritySection.size()) 698 .append(", ADDITIONAL: ").append(additionalSection.size()) 699 .append("\n\n"); 700 for (Record<? extends Data> record : additionalSection) { 701 Edns edns = Edns.fromRecord(record); 702 if (edns != null) { 703 sb.append(";; OPT PSEUDOSECTION:\n; ").append(edns.asTerminalOutput()); 704 break; 705 } 706 } 707 if (questions.size() != 0) { 708 sb.append(";; QUESTION SECTION:\n"); 709 for (Question question : questions) { 710 sb.append(';').append(question.toString()).append('\n'); 711 } 712 } 713 if (authoritySection.size() != 0) { 714 sb.append("\n;; AUTHORITY SECTION:\n"); 715 for (Record<? extends Data> record : authoritySection) { 716 sb.append(record.toString()).append('\n'); 717 } 718 } 719 if (answerSection.size() != 0) { 720 sb.append("\n;; ANSWER SECTION:\n"); 721 for (Record<? extends Data> record : answerSection) { 722 sb.append(record.toString()).append('\n'); 723 } 724 } 725 if (additionalSection.size() != 0) { 726 boolean hasNonOptArr = false; 727 for (Record<? extends Data> record : additionalSection) { 728 if (record.type != Record.TYPE.OPT) { 729 if (!hasNonOptArr) { 730 hasNonOptArr = true; 731 sb.append("\n;; ADDITIONAL SECTION:\n"); 732 } 733 sb.append(record.toString()).append('\n'); 734 } 735 } 736 } 737 if (receiveTimestamp > 0) { 738 sb.append("\n;; WHEN: ").append(new Date(receiveTimestamp).toString()); 739 } 740 terminalOutputCache = sb.toString(); 741 return terminalOutputCache; 742 } 743 744 public <D extends Data> Set<D> getAnswersFor(Question q) { 745 if (responseCode != RESPONSE_CODE.NO_ERROR) return null; 746 747 // It would be great if we could verify that D matches q.type at this 748 // point. But on the other hand, if it does not, then the cast to D 749 // below will fail. 750 Set<D> res = new HashSet<>(answerSection.size()); 751 for (Record<? extends Data> record : answerSection) { 752 if (!record.isAnswer(q)) continue; 753 754 Data data = record.getPayload(); 755 @SuppressWarnings("unchecked") 756 D d = (D) data; 757 boolean isNew = res.add(d); 758 if (!isNew) { 759 LOGGER.log(Level.WARNING, "DnsMessage contains duplicate answers. Record: " + record + "; DnsMessage: " + this); 760 } 761 } 762 return res; 763 } 764 765 private long answersMinTtlCache = -1; 766 767 /** 768 * Get the minimum TTL from all answers in seconds. 769 * 770 * @return the minimum TTL from all answers in seconds. 771 */ 772 public long getAnswersMinTtl() { 773 if (answersMinTtlCache >= 0) { 774 return answersMinTtlCache; 775 } 776 777 answersMinTtlCache = Long.MAX_VALUE; 778 for (Record<? extends Data> r : answerSection) { 779 answersMinTtlCache = Math.min(answersMinTtlCache, r.ttl); 780 } 781 return answersMinTtlCache; 782 } 783 784 public Builder asBuilder() { 785 return new Builder(this); 786 } 787 788 private DnsMessage normalizedVersionCache; 789 790 public DnsMessage asNormalizedVersion() { 791 if (normalizedVersionCache == null) { 792 normalizedVersionCache = new DnsMessage(this); 793 } 794 return normalizedVersionCache; 795 } 796 797 public Builder getResponseBuilder(RESPONSE_CODE responseCode) { 798 if (qr) { 799 throw new IllegalStateException(); 800 } 801 Builder responseBuilder = DnsMessage.builder() 802 .setQrFlag(true) 803 .setResponseCode(responseCode) 804 .setId(id) 805 .setQuestion(getQuestion()); 806 807 return responseBuilder; 808 } 809 810 private transient Integer hashCodeCache; 811 812 @Override 813 public int hashCode() { 814 if (hashCodeCache == null) { 815 byte[] bytes = serialize(); 816 hashCodeCache = Arrays.hashCode(bytes); 817 } 818 return hashCodeCache; 819 } 820 821 private enum SectionName { 822 answer, 823 authority, 824 additional, 825 } 826 827 private <D extends Data> List<Record<D>> filterSectionByType(boolean stopOnFirst, SectionName sectionName, Class<D> type) { 828 List<Record<?>> sectionToFilter; 829 switch (sectionName) { 830 case answer: 831 sectionToFilter = answerSection; 832 break; 833 case authority: 834 sectionToFilter = authoritySection; 835 break; 836 case additional: 837 sectionToFilter = additionalSection; 838 break; 839 default: 840 throw new AssertionError("Unknown section name " + sectionName); 841 } 842 843 List<Record<D>> res = new ArrayList<>(stopOnFirst ? 1 : sectionToFilter.size()); 844 845 for (Record<?> record : sectionToFilter) { 846 Record<D> target = record.ifPossibleAs(type); 847 if (target != null) { 848 res.add(target); 849 if (stopOnFirst) { 850 return res; 851 } 852 } 853 } 854 855 return res; 856 } 857 858 private <D extends Data> List<Record<D>> filterSectionByType(SectionName sectionName, Class<D> type) { 859 return filterSectionByType(false, sectionName, type); 860 } 861 862 private <D extends Data> Record<D> getFirstOfType(SectionName sectionName, Class<D> type) { 863 List<Record<D>> result = filterSectionByType(true, sectionName, type); 864 if (result.isEmpty()) { 865 return null; 866 } 867 868 return result.get(0); 869 } 870 871 public <D extends Data> List<Record<D>> filterAnswerSectionBy(Class<D> type) { 872 return filterSectionByType(SectionName.answer, type); 873 } 874 875 public <D extends Data> List<Record<D>> filterAuthoritySectionBy(Class<D> type) { 876 return filterSectionByType(SectionName.authority, type); 877 } 878 879 public <D extends Data> List<Record<D>> filterAdditionalSectionBy(Class<D> type) { 880 return filterSectionByType(SectionName.additional, type); 881 } 882 883 public <D extends Data> Record<D> getFirstOfTypeFromAnswerSection(Class<D> type) { 884 return getFirstOfType(SectionName.answer, type); 885 } 886 887 public <D extends Data> Record<D> getFirstOfTypeFromAuthoritySection(Class<D> type) { 888 return getFirstOfType(SectionName.authority, type); 889 } 890 891 public <D extends Data> Record<D> getFirstOfTypeFromAdditionalSection(Class<D> type) { 892 return getFirstOfType(SectionName.additional, type); 893 } 894 895 @Override 896 public boolean equals(Object other) { 897 if (!(other instanceof DnsMessage)) { 898 return false; 899 } 900 if (other == this) { 901 return true; 902 } 903 DnsMessage otherDnsMessage = (DnsMessage) other; 904 byte[] otherBytes = otherDnsMessage.serialize(); 905 byte[] myBytes = serialize(); 906 return Arrays.equals(myBytes, otherBytes); 907 } 908 909 public static Builder builder() { 910 return new DnsMessage.Builder(); 911 } 912 913 public static final class Builder { 914 915 private Builder() { 916 } 917 918 private Builder(DnsMessage message) { 919 id = message.id; 920 opcode = message.opcode; 921 responseCode = message.responseCode; 922 query = message.qr; 923 authoritativeAnswer = message.authoritativeAnswer; 924 truncated = message.truncated; 925 recursionDesired = message.recursionDesired; 926 recursionAvailable = message.recursionAvailable; 927 authenticData = message.authenticData; 928 checkingDisabled = message.checkingDisabled; 929 receiveTimestamp = message.receiveTimestamp; 930 931 // Copy the unmodifiable lists over into this new builder. 932 questions = new ArrayList<>(message.questions.size()); 933 questions.addAll(message.questions); 934 answerSection = new ArrayList<>(message.answerSection.size()); 935 answerSection.addAll(message.answerSection); 936 authoritySection = new ArrayList<>(message.authoritySection.size()); 937 authoritySection.addAll(message.authoritySection); 938 additionalSection = new ArrayList<>(message.additionalSection.size()); 939 additionalSection.addAll(message.additionalSection); 940 } 941 942 private int id; 943 private OPCODE opcode = OPCODE.QUERY; 944 private RESPONSE_CODE responseCode = RESPONSE_CODE.NO_ERROR; 945 private boolean query; 946 private boolean authoritativeAnswer; 947 private boolean truncated; 948 private boolean recursionDesired; 949 private boolean recursionAvailable; 950 private boolean authenticData; 951 private boolean checkingDisabled; 952 953 private long receiveTimestamp = -1; 954 955 private List<Question> questions; 956 private List<Record<? extends Data>> answerSection; 957 private List<Record<? extends Data>> authoritySection; 958 private List<Record<? extends Data>> additionalSection; 959 private Edns.Builder ednsBuilder; 960 961 /** 962 * Set the current DNS message id. 963 * 964 * @param id The new DNS message id. 965 * @return a reference to this builder. 966 */ 967 public Builder setId(int id) { 968 this.id = id & 0xffff; 969 return this; 970 } 971 972 public Builder setOpcode(OPCODE opcode) { 973 this.opcode = opcode; 974 return this; 975 } 976 977 public Builder setResponseCode(RESPONSE_CODE responseCode) { 978 this.responseCode = responseCode; 979 return this; 980 } 981 982 /** 983 * Set the QR flag. Note that this will be <code>true</code> if the message is a 984 * <b>response</b> and <code>false</code> if it is a <b>query</b>. 985 * 986 * @param query The new QR flag status. 987 * @return a reference to this builder. 988 */ 989 public Builder setQrFlag(boolean query) { 990 this.query = query; 991 return this; 992 } 993 994 /** 995 * Set the authoritative answer flag. 996 * 997 * @param authoritativeAnswer Tge new authoritative answer value. 998 * @return a reference to this builder. 999 */ 1000 public Builder setAuthoritativeAnswer(boolean authoritativeAnswer) { 1001 this.authoritativeAnswer = authoritativeAnswer; 1002 return this; 1003 } 1004 1005 /** 1006 * Set the truncation bit on this DNS message. 1007 * 1008 * @param truncated The new truncated bit status. 1009 * @return a reference to this builder. 1010 */ 1011 public Builder setTruncated(boolean truncated) { 1012 this.truncated = truncated; 1013 return this; 1014 } 1015 1016 /** 1017 * Set the recursion desired flag on this message. 1018 * 1019 * @param recursionDesired The new recusrion setting. 1020 * @return a reference to this builder. 1021 */ 1022 public Builder setRecursionDesired(boolean recursionDesired) { 1023 this.recursionDesired = recursionDesired; 1024 return this; 1025 } 1026 1027 /** 1028 * Set the recursion available flog from this DNS message. 1029 * 1030 * @param recursionAvailable The new recursion available status. 1031 * @return a reference to this builder. 1032 */ 1033 public Builder setRecursionAvailable(boolean recursionAvailable) { 1034 this.recursionAvailable = recursionAvailable; 1035 return this; 1036 } 1037 1038 /** 1039 * Set the authentic data flag on this DNS message. 1040 * 1041 * @param authenticData The new authentic data flag value. 1042 * @return a reference to this builder. 1043 */ 1044 public Builder setAuthenticData(boolean authenticData) { 1045 this.authenticData = authenticData; 1046 return this; 1047 } 1048 1049 /** 1050 * Change the check status of this packet. 1051 * 1052 * @param checkingDisabled The new check disabled value. 1053 * @return a reference to this builder. 1054 */ 1055 @Deprecated 1056 public Builder setCheckDisabled(boolean checkingDisabled) { 1057 this.checkingDisabled = checkingDisabled; 1058 return this; 1059 } 1060 1061 /** 1062 * Change the check status of this packet. 1063 * 1064 * @param checkingDisabled The new check disabled value. 1065 * @return a reference to this builder. 1066 */ 1067 public Builder setCheckingDisabled(boolean checkingDisabled) { 1068 this.checkingDisabled = checkingDisabled; 1069 return this; 1070 } 1071 1072 public void copyFlagsFrom(DnsMessage dnsMessage) { 1073 this.query = dnsMessage.qr; 1074 this.authoritativeAnswer = dnsMessage.authenticData; 1075 this.truncated = dnsMessage.truncated; 1076 this.recursionDesired = dnsMessage.recursionDesired; 1077 this.recursionAvailable = dnsMessage.recursionAvailable; 1078 this.authenticData = dnsMessage.authenticData; 1079 this.checkingDisabled = dnsMessage.checkingDisabled; 1080 } 1081 1082 public Builder setReceiveTimestamp(long receiveTimestamp) { 1083 this.receiveTimestamp = receiveTimestamp; 1084 return this; 1085 } 1086 1087 public Builder addQuestion(Question question) { 1088 if (questions == null) { 1089 questions = new ArrayList<>(1); 1090 } 1091 questions.add(question); 1092 return this; 1093 } 1094 1095 /** 1096 * Set the question part of this message. 1097 * 1098 * @param questions The questions. 1099 * @return a reference to this builder. 1100 */ 1101 public Builder setQuestions(List<Question> questions) { 1102 this.questions = questions; 1103 return this; 1104 } 1105 1106 /** 1107 * Set the question part of this message. 1108 * 1109 * @param question The question. 1110 * @return a reference to this builder. 1111 */ 1112 public Builder setQuestion(Question question) { 1113 this.questions = new ArrayList<>(1); 1114 this.questions.add(question); 1115 return this; 1116 } 1117 1118 public Builder addAnswer(Record<? extends Data> answer) { 1119 if (answerSection == null) { 1120 answerSection = new ArrayList<>(1); 1121 } 1122 answerSection.add(answer); 1123 return this; 1124 } 1125 1126 public Builder addAnswers(Collection<Record<? extends Data>> records) { 1127 if (answerSection == null) { 1128 answerSection = new ArrayList<>(records.size()); 1129 } 1130 answerSection.addAll(records); 1131 return this; 1132 } 1133 1134 public Builder setAnswers(Collection<Record<? extends Data>> records) { 1135 answerSection = new ArrayList<>(records.size()); 1136 answerSection.addAll(records); 1137 return this; 1138 } 1139 1140 public List<Record<? extends Data>> getAnswers() { 1141 if (answerSection == null) { 1142 return Collections.emptyList(); 1143 } 1144 return answerSection; 1145 } 1146 1147 public Builder addNameserverRecords(Record<? extends Data> record) { 1148 if (authoritySection == null) { 1149 authoritySection = new ArrayList<>(8); 1150 } 1151 authoritySection.add(record); 1152 return this; 1153 } 1154 1155 public Builder setNameserverRecords(Collection<Record<? extends Data>> records) { 1156 authoritySection = new ArrayList<>(records.size()); 1157 authoritySection.addAll(records); 1158 return this; 1159 } 1160 1161 public Builder setAdditionalResourceRecords(Collection<Record<? extends Data>> records) { 1162 additionalSection = new ArrayList<>(records.size()); 1163 additionalSection.addAll(records); 1164 return this; 1165 } 1166 1167 public Builder addAdditionalResourceRecord(Record<? extends Data> record) { 1168 if (additionalSection == null) { 1169 additionalSection = new ArrayList<>(); 1170 } 1171 additionalSection.add(record); 1172 return this; 1173 } 1174 1175 public Builder addAdditionalResourceRecords(List<Record<? extends Data>> records) { 1176 if (additionalSection == null) { 1177 additionalSection = new ArrayList<>(records.size()); 1178 } 1179 additionalSection.addAll(records); 1180 return this; 1181 } 1182 1183 public List<Record<? extends Data>> getAdditionalResourceRecords() { 1184 if (additionalSection == null) { 1185 return Collections.emptyList(); 1186 } 1187 return additionalSection; 1188 } 1189 1190 /** 1191 * Get the {@link Edns} builder. If no builder has been set so far, then a new one will be created. 1192 * <p> 1193 * The EDNS record can be used to announce the supported size of UDP payload as well as additional flags. 1194 * </p> 1195 * <p> 1196 * Note that some networks and firewalls are known to block big UDP payloads. 1280 should be a reasonable value, 1197 * everything below 512 is treated as 512 and should work on all networks. 1198 * </p> 1199 * 1200 * @return a EDNS builder. 1201 */ 1202 public Edns.Builder getEdnsBuilder() { 1203 if (ednsBuilder == null) { 1204 ednsBuilder = Edns.builder(); 1205 } 1206 return ednsBuilder; 1207 } 1208 1209 public DnsMessage build() { 1210 return new DnsMessage(this); 1211 } 1212 1213 private void writeToStringBuilder(StringBuilder sb) { 1214 sb.append('(') 1215 .append(id) 1216 .append(' ') 1217 .append(opcode) 1218 .append(' ') 1219 .append(responseCode) 1220 .append(' '); 1221 if (query) { 1222 sb.append("resp[qr=1]"); 1223 } else { 1224 sb.append("query[qr=0]"); 1225 } 1226 if (authoritativeAnswer) 1227 sb.append(" aa"); 1228 if (truncated) 1229 sb.append(" tr"); 1230 if (recursionDesired) 1231 sb.append(" rd"); 1232 if (recursionAvailable) 1233 sb.append(" ra"); 1234 if (authenticData) 1235 sb.append(" ad"); 1236 if (checkingDisabled) 1237 sb.append(" cd"); 1238 sb.append(")\n"); 1239 if (questions != null) { 1240 for (Question question : questions) { 1241 sb.append("[Q: ").append(question).append("]\n"); 1242 } 1243 } 1244 if (answerSection != null) { 1245 for (Record<? extends Data> record : answerSection) { 1246 sb.append("[A: ").append(record).append("]\n"); 1247 } 1248 } 1249 if (authoritySection != null) { 1250 for (Record<? extends Data> record : authoritySection) { 1251 sb.append("[N: ").append(record).append("]\n"); 1252 } 1253 } 1254 if (additionalSection != null) { 1255 for (Record<? extends Data> record : additionalSection) { 1256 sb.append("[X: "); 1257 Edns edns = Edns.fromRecord(record); 1258 if (edns != null) { 1259 sb.append(edns.toString()); 1260 } else { 1261 sb.append(record); 1262 } 1263 sb.append("]\n"); 1264 } 1265 } 1266 1267 // Strip trailing newline. 1268 if (sb.charAt(sb.length() - 1) == '\n') { 1269 sb.setLength(sb.length() - 1); 1270 } 1271 } 1272 1273 @Override 1274 public String toString() { 1275 StringBuilder sb = new StringBuilder("Builder of DnsMessage"); 1276 writeToStringBuilder(sb); 1277 return sb.toString(); 1278 } 1279 } 1280 1281}