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