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    *   http://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.buffer.ByteBufUtil;
20  import io.netty.channel.socket.InternetProtocolFamily;
21  import io.netty.handler.codec.UnsupportedMessageTypeException;
22  import io.netty.util.internal.StringUtil;
23  import io.netty.util.internal.UnstableApi;
24  
25  import static io.netty.handler.codec.dns.DefaultDnsRecordDecoder.ROOT;
26  
27  /**
28   * The default {@link DnsRecordEncoder} implementation.
29   *
30   * @see DefaultDnsRecordDecoder
31   */
32  @UnstableApi
33  public class DefaultDnsRecordEncoder implements DnsRecordEncoder {
34      private static final int PREFIX_MASK = Byte.SIZE - 1;
35  
36      /**
37       * Creates a new instance.
38       */
39      protected DefaultDnsRecordEncoder() { }
40  
41      @Override
42      public final void encodeQuestion(DnsQuestion question, ByteBuf out) throws Exception {
43          encodeName(question.name(), out);
44          out.writeShort(question.type().intValue());
45          out.writeShort(question.dnsClass());
46      }
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(StringUtil.simpleClassName(record));
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 http://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         if (ROOT.equals(name)) {
145             // Root domain
146             buf.writeByte(0);
147             return;
148         }
149 
150         final String[] labels = name.split("\\.");
151         for (String label : labels) {
152             final int labelLen = label.length();
153             if (labelLen == 0) {
154                 // zero-length label means the end of the name.
155                 break;
156             }
157 
158             buf.writeByte(labelLen);
159             ByteBufUtil.writeAscii(buf, label);
160         }
161 
162         buf.writeByte(0); // marks end of name field
163     }
164 
165     private static byte padWithZeros(byte b, int lowOrderBitsToPreserve) {
166         switch (lowOrderBitsToPreserve) {
167         case 0:
168             return 0;
169         case 1:
170             return (byte) (0x80 & b);
171         case 2:
172             return (byte) (0xC0 & b);
173         case 3:
174             return (byte) (0xE0 & b);
175         case 4:
176             return (byte) (0xF0 & b);
177         case 5:
178             return (byte) (0xF8 & b);
179         case 6:
180             return (byte) (0xFC & b);
181         case 7:
182             return (byte) (0xFE & b);
183         case 8:
184             return b;
185         default:
186             throw new IllegalArgumentException("lowOrderBitsToPreserve: " + lowOrderBitsToPreserve);
187         }
188     }
189 }