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  import io.netty.util.internal.UnstableApi;
22  
23  /**
24   * The default {@link DnsRecordEncoder} implementation.
25   *
26   * @see DefaultDnsRecordDecoder
27   */
28  @UnstableApi
29  public class DefaultDnsRecordEncoder implements DnsRecordEncoder {
30      private static final int PREFIX_MASK = Byte.SIZE - 1;
31  
32      /**
33       * Creates a new instance.
34       */
35      protected DefaultDnsRecordEncoder() { }
36  
37      @Override
38      public final void encodeQuestion(DnsQuestion question, ByteBuf out) throws Exception {
39          encodeName(question.name(), out);
40          out.writeShort(question.type().intValue());
41          out.writeShort(question.dnsClass());
42      }
43  
44      private static final Class<?>[] SUPPORTED_MESSAGES = new Class<?>[] {
45              DnsQuestion.class, DnsPtrRecord.class,
46              DnsOptEcsRecord.class, DnsOptPseudoRecord.class, DnsRawRecord.class };
47  
48      @Override
49      public void encodeRecord(DnsRecord record, ByteBuf out) throws Exception {
50          if (record instanceof DnsQuestion) {
51              encodeQuestion((DnsQuestion) record, out);
52          } else if (record instanceof DnsPtrRecord) {
53              encodePtrRecord((DnsPtrRecord) record, out);
54          } else if (record instanceof DnsOptEcsRecord) {
55              encodeOptEcsRecord((DnsOptEcsRecord) record, out);
56          } else if (record instanceof DnsOptPseudoRecord) {
57              encodeOptPseudoRecord((DnsOptPseudoRecord) record, out);
58          } else if (record instanceof DnsRawRecord) {
59              encodeRawRecord((DnsRawRecord) record, out);
60          } else {
61              throw new UnsupportedMessageTypeException(record, SUPPORTED_MESSAGES);
62          }
63      }
64  
65      private void encodeRecord0(DnsRecord record, ByteBuf out) throws Exception {
66          encodeName(record.name(), out);
67          out.writeShort(record.type().intValue());
68          out.writeShort(record.dnsClass());
69          out.writeInt((int) record.timeToLive());
70      }
71  
72      private void encodePtrRecord(DnsPtrRecord record, ByteBuf out) throws Exception {
73          encodeRecord0(record, out);
74          encodeName(record.hostname(), out);
75      }
76  
77      private void encodeOptPseudoRecord(DnsOptPseudoRecord record, ByteBuf out) throws Exception {
78          encodeRecord0(record, out);
79          out.writeShort(0);
80      }
81  
82      private void encodeOptEcsRecord(DnsOptEcsRecord record, ByteBuf out) throws Exception {
83          encodeRecord0(record, out);
84  
85          int sourcePrefixLength = record.sourcePrefixLength();
86          int scopePrefixLength = record.scopePrefixLength();
87          int lowOrderBitsToPreserve = sourcePrefixLength & PREFIX_MASK;
88  
89          byte[] bytes = record.address();
90          int addressBits = bytes.length << 3;
91          if (addressBits < sourcePrefixLength || sourcePrefixLength < 0) {
92              throw new IllegalArgumentException(sourcePrefixLength + ": " +
93                      sourcePrefixLength + " (expected: 0 >= " + addressBits + ')');
94          }
95  
96          // See https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml
97          final short addressNumber = (short) (bytes.length == 4 ?
98                  InternetProtocolFamily.IPv4.addressNumber() : InternetProtocolFamily.IPv6.addressNumber());
99          int payloadLength = calculateEcsAddressLength(sourcePrefixLength, lowOrderBitsToPreserve);
100 
101         int fullPayloadLength = 2 + // OPTION-CODE
102                 2 + // OPTION-LENGTH
103                 2 + // FAMILY
104                 1 + // SOURCE PREFIX-LENGTH
105                 1 + // SCOPE PREFIX-LENGTH
106                 payloadLength; //  ADDRESS...
107 
108         out.writeShort(fullPayloadLength);
109         out.writeShort(8); // This is the defined type for ECS.
110 
111         out.writeShort(fullPayloadLength - 4); // Not include OPTION-CODE and OPTION-LENGTH
112         out.writeShort(addressNumber);
113         out.writeByte(sourcePrefixLength);
114         out.writeByte(scopePrefixLength); // Must be 0 in queries.
115 
116         if (lowOrderBitsToPreserve > 0) {
117             int bytesLength = payloadLength - 1;
118             out.writeBytes(bytes, 0, bytesLength);
119 
120             // Pad the leftover of the last byte with zeros.
121             out.writeByte(padWithZeros(bytes[bytesLength], lowOrderBitsToPreserve));
122         } else {
123             // The sourcePrefixLength align with Byte so just copy in the bytes directly.
124             out.writeBytes(bytes, 0, payloadLength);
125         }
126     }
127 
128     // Package-Private for testing
129     static int calculateEcsAddressLength(int sourcePrefixLength, int lowOrderBitsToPreserve) {
130         return (sourcePrefixLength >>> 3) + (lowOrderBitsToPreserve != 0 ? 1 : 0);
131     }
132 
133     private void encodeRawRecord(DnsRawRecord record, ByteBuf out) throws Exception {
134         encodeRecord0(record, out);
135 
136         ByteBuf content = record.content();
137         int contentLen = content.readableBytes();
138 
139         out.writeShort(contentLen);
140         out.writeBytes(content, content.readerIndex(), contentLen);
141     }
142 
143     protected void encodeName(String name, ByteBuf buf) throws Exception {
144         DnsCodecUtil.encodeDomainName(name, buf);
145     }
146 
147     private static byte padWithZeros(byte b, int lowOrderBitsToPreserve) {
148         switch (lowOrderBitsToPreserve) {
149         case 0:
150             return 0;
151         case 1:
152             return (byte) (0x80 & b);
153         case 2:
154             return (byte) (0xC0 & b);
155         case 3:
156             return (byte) (0xE0 & b);
157         case 4:
158             return (byte) (0xF0 & b);
159         case 5:
160             return (byte) (0xF8 & b);
161         case 6:
162             return (byte) (0xFC & b);
163         case 7:
164             return (byte) (0xFE & b);
165         case 8:
166             return b;
167         default:
168             throw new IllegalArgumentException("lowOrderBitsToPreserve: " + lowOrderBitsToPreserve);
169         }
170     }
171 }