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  
17  package io.netty5.resolver.dns;
18  
19  import io.netty5.util.NetUtil;
20  import io.netty5.util.collection.IntObjectHashMap;
21  import io.netty5.util.collection.IntObjectMap;
22  
23  import java.net.Inet4Address;
24  import java.net.Inet6Address;
25  import java.net.InetAddress;
26  import java.net.InetSocketAddress;
27  import java.net.UnknownHostException;
28  import java.util.HashMap;
29  import java.util.Map;
30  import java.util.concurrent.ThreadLocalRandom;
31  
32  final class DnsQueryContextManager {
33  
34      /**
35       * A map whose key is the DNS server address and value is the map of the DNS query ID and its corresponding
36       * {@link DnsQueryContext}.
37       */
38      final Map<InetSocketAddress, IntObjectMap<DnsQueryContext>> map = new HashMap<>();
39  
40      int add(DnsQueryContext qCtx) {
41          final IntObjectMap<DnsQueryContext> contexts = getOrCreateContextMap(qCtx.nameServerAddr());
42  
43          int id = ThreadLocalRandom.current().nextInt(65536 - 1) + 1;
44          final int maxTries = 65535 << 1;
45          int tries = 0;
46  
47          synchronized (contexts) {
48              for (;;) {
49                  if (!contexts.containsKey(id)) {
50                      contexts.put(id, qCtx);
51                      return id;
52                  }
53  
54                  id = id + 1 & 0xFFFF;
55  
56                  if (++tries >= maxTries) {
57                      throw new IllegalStateException("query ID space exhausted: " + qCtx.question());
58                  }
59              }
60          }
61      }
62  
63      DnsQueryContext get(InetSocketAddress nameServerAddr, int id) {
64          final IntObjectMap<DnsQueryContext> contexts = getContextMap(nameServerAddr);
65          final DnsQueryContext qCtx;
66          if (contexts != null) {
67              synchronized (contexts) {
68                  qCtx = contexts.get(id);
69              }
70          } else {
71              qCtx = null;
72          }
73  
74          return qCtx;
75      }
76  
77      DnsQueryContext remove(InetSocketAddress nameServerAddr, int id) {
78          final IntObjectMap<DnsQueryContext> contexts = getContextMap(nameServerAddr);
79          if (contexts == null) {
80              return null;
81          }
82  
83          synchronized (contexts) {
84              return  contexts.remove(id);
85          }
86      }
87  
88      private IntObjectMap<DnsQueryContext> getContextMap(InetSocketAddress nameServerAddr) {
89          synchronized (map) {
90              return map.get(nameServerAddr);
91          }
92      }
93  
94      private IntObjectMap<DnsQueryContext> getOrCreateContextMap(InetSocketAddress nameServerAddr) {
95          synchronized (map) {
96              final IntObjectMap<DnsQueryContext> contexts = map.get(nameServerAddr);
97              if (contexts != null) {
98                  return contexts;
99              }
100 
101             final IntObjectMap<DnsQueryContext> newContexts = new IntObjectHashMap<DnsQueryContext>();
102             final InetAddress a = nameServerAddr.getAddress();
103             final int port = nameServerAddr.getPort();
104             map.put(nameServerAddr, newContexts);
105 
106             if (a instanceof Inet4Address) {
107                 // Also add the mapping for the IPv4-compatible IPv6 address.
108                 final Inet4Address a4 = (Inet4Address) a;
109                 if (a4.isLoopbackAddress()) {
110                     map.put(new InetSocketAddress(NetUtil.LOCALHOST6, port), newContexts);
111                 } else {
112                     map.put(new InetSocketAddress(toCompactAddress(a4), port), newContexts);
113                 }
114             } else if (a instanceof Inet6Address) {
115                 // Also add the mapping for the IPv4 address if this IPv6 address is compatible.
116                 final Inet6Address a6 = (Inet6Address) a;
117                 if (a6.isLoopbackAddress()) {
118                     map.put(new InetSocketAddress(NetUtil.LOCALHOST4, port), newContexts);
119                 } else if (a6.isIPv4CompatibleAddress()) {
120                     map.put(new InetSocketAddress(toIPv4Address(a6), port), newContexts);
121                 }
122             }
123 
124             return newContexts;
125         }
126     }
127 
128     private static Inet6Address toCompactAddress(Inet4Address a4) {
129         byte[] b4 = a4.getAddress();
130         byte[] b6 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, b4[0], b4[1], b4[2], b4[3] };
131         try {
132             return (Inet6Address) InetAddress.getByAddress(b6);
133         } catch (UnknownHostException e) {
134             throw new Error(e);
135         }
136     }
137 
138     private static Inet4Address toIPv4Address(Inet6Address a6) {
139         byte[] b6 = a6.getAddress();
140         byte[] b4 = { b6[12], b6[13], b6[14], b6[15] };
141         try {
142             return (Inet4Address) InetAddress.getByAddress(b4);
143         } catch (UnknownHostException e) {
144             throw new Error(e);
145         }
146     }
147 }