View Javadoc
1   /*
2    * Copyright 2015 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.dns;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.socket.InternetProtocolFamily;
20  import io.netty.handler.codec.UnsupportedMessageTypeException;
21  
22  /**
23   * The default {@link DnsRecordEncoder} implementation.
24   *
25   * @see DefaultDnsRecordDecoder
26   */
27  public class DefaultDnsRecordEncoder implements DnsRecordEncoder {
28      private static final int PREFIX_MASK = Byte.SIZE - 1;
29  
30      /**
31       * Creates a new instance.
32       */
33      protected DefaultDnsRecordEncoder() { }
34  
35      @Override
36      public final void encodeQuestion(DnsQuestion question, ByteBuf out) throws Exception {
37          encodeName(question.name(), out);
38          out.writeShort(question.type().intValue());
39          out.writeShort(question.dnsClass());
40      }
41  
42      private static final Class<?>[] SUPPORTED_MESSAGES = new Class<?>[] {
43              DnsQuestion.class, DnsPtrRecord.class,
44              DnsOptEcsRecord.class, DnsOptPseudoRecord.class, DnsRawRecord.class };
45  
46      @Override
47      public void encodeRecord(DnsRecord record, ByteBuf out) throws Exception {
48          if (record instanceof DnsQuestion) {
49              encodeQuestion((DnsQuestion) record, out);
50          } else if (record instanceof DnsPtrRecord) {
51              encodePtrRecord((DnsPtrRecord) record, out);
52          } else if (record instanceof DnsOptEcsRecord) {
53              encodeOptEcsRecord((DnsOptEcsRecord) record, out);
54          } else if (record instanceof DnsOptPseudoRecord) {
55              encodeOptPseudoRecord((DnsOptPseudoRecord) record, out);
56          } else if (record instanceof DnsRawRecord) {
57              encodeRawRecord((DnsRawRecord) record, out);
58          } else {
59              throw new UnsupportedMessageTypeException(record, SUPPORTED_MESSAGES);
60          }
61      }
62  
63      private void encodeRecord0(DnsRecord record, ByteBuf out) throws Exception {
64          encodeName(record.name(), out);
65          out.writeShort(record.type().intValue());
66          out.writeShort(record.dnsClass());
67          out.writeInt((int) record.timeToLive());
68      }
69  
70      private void encodePtrRecord(DnsPtrRecord record, ByteBuf out) throws Exception {
71          encodeRecord0(record, out);
72          int writerIndex = out.writerIndex();
73          // Skip 2 bytes as these will be used to encode the rdataLen after we know how many bytes were written.
74          // See https://www.rfc-editor.org/rfc/rfc1035.html#section-3.2.1
75          out.writerIndex(writerIndex + 2);
76          encodeName(record.hostname(), out);
77          int rdLength = out.writerIndex() - (writerIndex + 2);
78          out.setShort(writerIndex, rdLength);
79      }
80  
81      private void encodeOptPseudoRecord(DnsOptPseudoRecord record, ByteBuf out) throws Exception {
82          encodeRecord0(record, out);
83          out.writeShort(0);
84      }
85  
86      private void encodeOptEcsRecord(DnsOptEcsRecord record, ByteBuf out) throws Exception {
87          encodeRecord0(record, out);
88  
89          int sourcePrefixLength = record.sourcePrefixLength();
90          int scopePrefixLength = record.scopePrefixLength();
91          int lowOrderBitsToPreserve = sourcePrefixLength & PREFIX_MASK;
92  
93          byte[] bytes = record.address();
94          int addressBits = bytes.length << 3;
95          if (addressBits < sourcePrefixLength || sourcePrefixLength < 0) {
96              throw new IllegalArgumentException(sourcePrefixLength + ": " +
97                      sourcePrefixLength + " (expected: 0 >= " + addressBits + ')');
98          }
99  
100         // See https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml
101         final short addressNumber = (short) (bytes.length == 4 ?
102                 InternetProtocolFamily.IPv4.addressNumber() : InternetProtocolFamily.IPv6.addressNumber());
103         int payloadLength = calculateEcsAddressLength(sourcePrefixLength, lowOrderBitsToPreserve);
104 
105         int fullPayloadLength = 2 + // OPTION-CODE
106                 2 + // OPTION-LENGTH
107                 2 + // FAMILY
108                 1 + // SOURCE PREFIX-LENGTH
109                 1 + // SCOPE PREFIX-LENGTH
110                 payloadLength; //  ADDRESS...
111 
112         out.writeShort(fullPayloadLength);
113         out.writeShort(8); // This is the defined type for ECS.
114 
115         out.writeShort(fullPayloadLength - 4); // Not include OPTION-CODE and OPTION-LENGTH
116         out.writeShort(addressNumber);
117         out.writeByte(sourcePrefixLength);
118         out.writeByte(scopePrefixLength); // Must be 0 in queries.
119 
120         if (lowOrderBitsToPreserve > 0) {
121             int bytesLength = payloadLength - 1;
122             out.writeBytes(bytes, 0, bytesLength);
123 
124             // Pad the leftover of the last byte with zeros.
125             out.writeByte(padWithZeros(bytes[bytesLength], lowOrderBitsToPreserve));
126         } else {
127             // The sourcePrefixLength align with Byte so just copy in the bytes directly.
128             out.writeBytes(bytes, 0, payloadLength);
129         }
130     }
131 
132     // Package-Private for testing
133     static int calculateEcsAddressLength(int sourcePrefixLength, int lowOrderBitsToPreserve) {
134         return (sourcePrefixLength >>> 3) + (lowOrderBitsToPreserve != 0 ? 1 : 0);
135     }
136 
137     private void encodeRawRecord(DnsRawRecord record, ByteBuf out) throws Exception {
138         encodeRecord0(record, out);
139 
140         ByteBuf content = record.content();
141         int contentLen = content.readableBytes();
142 
143         out.writeShort(contentLen);
144         out.writeBytes(content, content.readerIndex(), contentLen);
145     }
146 
147     protected void encodeName(String name, ByteBuf buf) throws Exception {
148         DnsCodecUtil.encodeDomainName(name, buf);
149     }
150 
151     private static byte padWithZeros(byte b, int lowOrderBitsToPreserve) {
152         switch (lowOrderBitsToPreserve) {
153         case 0:
154             return 0;
155         case 1:
156             return (byte) (0x80 & b);
157         case 2:
158             return (byte) (0xC0 & b);
159         case 3:
160             return (byte) (0xE0 & b);
161         case 4:
162             return (byte) (0xF0 & b);
163         case 5:
164             return (byte) (0xF8 & b);
165         case 6:
166             return (byte) (0xFC & b);
167         case 7:
168             return (byte) (0xFE & b);
169         case 8:
170             return b;
171         default:
172             throw new IllegalArgumentException("lowOrderBitsToPreserve: " + lowOrderBitsToPreserve);
173         }
174     }
175 }