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