View Javadoc
1   /*
2    * Copyright 2019 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  
17  package io.netty5.handler.codec.dns;
18  
19  import io.netty5.buffer.api.Buffer;
20  import io.netty5.buffer.api.BufferAllocator;
21  import io.netty5.channel.socket.SocketProtocolFamily;
22  import io.netty5.handler.codec.CorruptedFrameException;
23  
24  import java.nio.charset.StandardCharsets;
25  
26  import static io.netty5.handler.codec.dns.DefaultDnsRecordDecoder.ROOT;
27  
28  final class DnsCodecUtil {
29      private DnsCodecUtil() {
30          // Util class
31      }
32  
33      static void encodeDomainName(String name, Buffer buf) {
34          if (ROOT.equals(name)) {
35              // Root domain
36              buf.ensureWritable(1);
37              buf.writeByte((byte) 0);
38              return;
39          }
40  
41          // Buffer size: For every name part, a dot is replaced by a length, with +1 for terminal NUL byte.
42          buf.ensureWritable(name.length() + 1);
43  
44          final String[] labels = name.split("\\.");
45          for (String label : labels) {
46              final int labelLen = label.length();
47              if (labelLen == 0) {
48                  // zero-length label means the end of the name.
49                  break;
50              }
51              buf.writeByte((byte) labelLen);
52              buf.writeBytes(label.getBytes(StandardCharsets.US_ASCII));
53          }
54  
55          buf.writeByte((byte) 0); // marks end of name field
56      }
57  
58      static String decodeDomainName(Buffer in) {
59          int position = -1;
60          int checked = 0;
61          final int end = in.writerOffset();
62          final int readable = in.readableBytes();
63  
64          // Looking at the spec we should always have at least enough readable bytes to read a byte here but it seems
65          // some servers do not respect this for empty names. So just workaround this and return an empty name in this
66          // case.
67          //
68          // See:
69          // - https://github.com/netty/netty/issues/5014
70          // - https://www.ietf.org/rfc/rfc1035.txt , Section 3.1
71          if (readable == 0) {
72              return ROOT;
73          }
74  
75          final StringBuilder name = new StringBuilder(readable << 1);
76          while (in.readableBytes() > 0) {
77              final int len = in.readUnsignedByte();
78              final boolean pointer = (len & 0xc0) == 0xc0;
79              if (pointer) {
80                  if (position == -1) {
81                      position = in.readerOffset() + 1;
82                  }
83  
84                  if (in.readableBytes() == 0) {
85                      throw new CorruptedFrameException("truncated pointer in a name");
86                  }
87  
88                  final int next = (len & 0x3f) << 8 | in.readUnsignedByte();
89                  if (next >= end) {
90                      throw new CorruptedFrameException("name has an out-of-range pointer");
91                  }
92                  in.readerOffset(next);
93  
94                  // check for loops
95                  checked += 2;
96                  if (checked >= end) {
97                      throw new CorruptedFrameException("name contains a loop.");
98                  }
99              } else if (len != 0) {
100                 if (in.readableBytes() < len) {
101                     throw new CorruptedFrameException("truncated label in a name");
102                 }
103                 name.append(in.readCharSequence(len, StandardCharsets.UTF_8)).append('.');
104             } else { // len == 0
105                 break;
106             }
107         }
108 
109         if (position != -1) {
110             in.readerOffset(position);
111         }
112 
113         if (name.length() == 0) {
114             return ROOT;
115         }
116 
117         if (name.charAt(name.length() - 1) != '.') {
118             name.append('.');
119         }
120 
121         return name.toString();
122     }
123 
124     /**
125      * Decompress pointer data.
126      * @param compression compressed data
127      * @return decompressed data
128      */
129     static Buffer decompressDomainName(BufferAllocator allocator, Buffer compression) {
130         String domainName = decodeDomainName(compression);
131         Buffer result = allocator.allocate(domainName.length() << 1);
132         encodeDomainName(domainName, result);
133         return result;
134     }
135 
136     /**
137      * Returns the
138      * <a href="https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml">address number</a>
139      * of the family.
140      */
141     static int addressNumber(SocketProtocolFamily family) {
142         switch (family) {
143             case INET:
144                 return 1;
145             case INET6:
146                 return 2;
147             default:
148                 throw new UnsupportedOperationException();
149         }
150     }
151 }