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