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