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.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  
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  
31  final class DnsQueryContextManager {
32  
33      /**
34       * A map whose key is the DNS server address and value is the map of the DNS query ID and its corresponding
35       * {@link DnsQueryContext}.
36       */
37      private final Map<InetSocketAddress, DnsQueryContextMap> map =
38              new HashMap<InetSocketAddress, DnsQueryContextMap>();
39  
40      /**
41       * Add {@link DnsQueryContext} to the context manager and return the ID that should be used for the query.
42       * This method will return {@code -1} if an ID could not be generated and the context was not stored.
43       *
44       * @param nameServerAddr    The {@link InetSocketAddress} of the nameserver to query.
45       * @param qCtx              The {@link {@link DnsQueryContext} to store.
46       * @return                  the ID that should be used or {@code -1} if none could be generated.
47       */
48      int add(InetSocketAddress nameServerAddr, DnsQueryContext qCtx) {
49          assert !nameServerAddr.isUnresolved();
50          final DnsQueryContextMap contexts = getOrCreateContextMap(nameServerAddr);
51          return contexts.add(qCtx);
52      }
53  
54      /**
55       * Return the {@link DnsQueryContext} for the given {@link InetSocketAddress} and id or {@code null} if
56       * none could be found.
57       *
58       * @param nameServerAddr    The {@link InetSocketAddress} of the nameserver.
59       * @param id                The id that identifies the {@link DnsQueryContext} and was used for the query.
60       * @return                  The context or {@code null} if none could be found.
61       */
62      DnsQueryContext get(InetSocketAddress nameServerAddr, int id) {
63          assert !nameServerAddr.isUnresolved();
64          final DnsQueryContextMap contexts = getContextMap(nameServerAddr);
65          if (contexts == null) {
66              return null;
67          }
68          return contexts.get(id);
69      }
70  
71      /**
72       * Remove the {@link DnsQueryContext} for the given {@link InetSocketAddress} and id or {@code null} if
73       * none could be found.
74       *
75       * @param nameServerAddr    The {@link InetSocketAddress} of the nameserver.
76       * @param id                The id that identifies the {@link DnsQueryContext} and was used for the query.
77       * @return                  The context or {@code null} if none could be removed.
78       */
79      DnsQueryContext remove(InetSocketAddress nameServerAddr, int id) {
80          assert !nameServerAddr.isUnresolved();
81          final DnsQueryContextMap contexts = getContextMap(nameServerAddr);
82          if (contexts == null) {
83              return null;
84          }
85          return contexts.remove(id);
86      }
87  
88      private DnsQueryContextMap getContextMap(InetSocketAddress nameServerAddr) {
89          synchronized (map) {
90              return map.get(nameServerAddr);
91          }
92      }
93  
94      private DnsQueryContextMap getOrCreateContextMap(InetSocketAddress nameServerAddr) {
95          synchronized (map) {
96              final DnsQueryContextMap contexts = map.get(nameServerAddr);
97              if (contexts != null) {
98                  return contexts;
99              }
100 
101             final DnsQueryContextMap newContexts = new DnsQueryContextMap();
102             final InetAddress a = nameServerAddr.getAddress();
103             final int port = nameServerAddr.getPort();
104             DnsQueryContextMap old = map.put(nameServerAddr, newContexts);
105             // Assert that we didn't replace an existing mapping.
106             assert old == null : "DnsQueryContextMap already exists for " + nameServerAddr;
107 
108             InetSocketAddress extraAddress = null;
109             if (a instanceof Inet4Address) {
110                 // Also add the mapping for the IPv4-compatible IPv6 address.
111                 final Inet4Address a4 = (Inet4Address) a;
112                 if (a4.isLoopbackAddress()) {
113                     extraAddress = new InetSocketAddress(NetUtil.LOCALHOST6, port);
114                 } else {
115                     extraAddress = new InetSocketAddress(toCompactAddress(a4), port);
116                 }
117             } else if (a instanceof Inet6Address) {
118                 // Also add the mapping for the IPv4 address if this IPv6 address is compatible.
119                 final Inet6Address a6 = (Inet6Address) a;
120                 if (a6.isLoopbackAddress()) {
121                     extraAddress = new InetSocketAddress(NetUtil.LOCALHOST4, port);
122                 } else if (a6.isIPv4CompatibleAddress()) {
123                     extraAddress = new InetSocketAddress(toIPv4Address(a6), port);
124                 }
125             }
126             if (extraAddress != null) {
127                 old = map.put(extraAddress, newContexts);
128                 // Assert that we didn't replace an existing mapping.
129                 assert old == null : "DnsQueryContextMap already exists for " + extraAddress;
130             }
131 
132             return newContexts;
133         }
134     }
135 
136     private static Inet6Address toCompactAddress(Inet4Address a4) {
137         byte[] b4 = a4.getAddress();
138         byte[] b6 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, b4[0], b4[1], b4[2], b4[3] };
139         try {
140             return (Inet6Address) InetAddress.getByAddress(b6);
141         } catch (UnknownHostException e) {
142             throw new Error(e);
143         }
144     }
145 
146     private static Inet4Address toIPv4Address(Inet6Address a6) {
147         assert a6.isIPv4CompatibleAddress();
148 
149         byte[] b6 = a6.getAddress();
150         byte[] b4 = { b6[12], b6[13], b6[14], b6[15] };
151         try {
152             return (Inet4Address) InetAddress.getByAddress(b4);
153         } catch (UnknownHostException e) {
154             throw new Error(e);
155         }
156     }
157 
158     private static final class DnsQueryContextMap {
159 
160         private final DnsQueryIdSpace idSpace = new DnsQueryIdSpace();
161 
162         // We increment on every usage so start with -1, this will ensure we start with 0 as first id.
163         private final IntObjectMap<DnsQueryContext> map = new IntObjectHashMap<DnsQueryContext>();
164 
165         synchronized int add(DnsQueryContext ctx) {
166             int id = idSpace.nextId();
167             if (id == -1) {
168                 // -1 means that we couldn't reserve an id to use. In this case return early and not store the
169                 // context in the map.
170                 return -1;
171             }
172             DnsQueryContext oldCtx = map.put(id, ctx);
173             assert oldCtx == null;
174             return id;
175         }
176 
177         synchronized DnsQueryContext get(int id) {
178             return map.get(id);
179         }
180 
181         synchronized DnsQueryContext remove(int id) {
182             DnsQueryContext result = map.remove(id);
183             if (result != null) {
184                 idSpace.pushId(id);
185             }
186             assert result != null : "DnsQueryContext not found, id: "  + id;
187             return result;
188         }
189     }
190 }