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          final DnsQueryContextMap contexts = getOrCreateContextMap(nameServerAddr);
50          return contexts.add(qCtx);
51      }
52  
53      /**
54       * Return the {@link DnsQueryContext} for the given {@link InetSocketAddress} and id or {@code null} if
55       * none could be found.
56       *
57       * @param nameServerAddr    The {@link InetSocketAddress} of the nameserver.
58       * @param id                The id that identifies the {@link DnsQueryContext} and was used for the query.
59       * @return                  The context or {@code null} if none could be found.
60       */
61      DnsQueryContext get(InetSocketAddress nameServerAddr, int id) {
62          final DnsQueryContextMap contexts = getContextMap(nameServerAddr);
63          if (contexts == null) {
64              return null;
65          }
66          return contexts.get(id);
67      }
68  
69      /**
70       * Remove the {@link DnsQueryContext} for the given {@link InetSocketAddress} and id or {@code null} if
71       * none could be found.
72       *
73       * @param nameServerAddr    The {@link InetSocketAddress} of the nameserver.
74       * @param id                The id that identifies the {@link DnsQueryContext} and was used for the query.
75       * @return                  The context or {@code null} if none could be removed.
76       */
77      DnsQueryContext remove(InetSocketAddress nameServerAddr, int id) {
78          final DnsQueryContextMap contexts = getContextMap(nameServerAddr);
79          if (contexts == null) {
80              return null;
81          }
82          return contexts.remove(id);
83      }
84  
85      private DnsQueryContextMap getContextMap(InetSocketAddress nameServerAddr) {
86          synchronized (map) {
87              return map.get(nameServerAddr);
88          }
89      }
90  
91      private DnsQueryContextMap getOrCreateContextMap(InetSocketAddress nameServerAddr) {
92          synchronized (map) {
93              final DnsQueryContextMap contexts = map.get(nameServerAddr);
94              if (contexts != null) {
95                  return contexts;
96              }
97  
98              final DnsQueryContextMap newContexts = new DnsQueryContextMap();
99              final InetAddress a = nameServerAddr.getAddress();
100             final int port = nameServerAddr.getPort();
101             DnsQueryContextMap old = map.put(nameServerAddr, newContexts);
102             // Assert that we didn't replace an existing mapping.
103             assert old == null : "DnsQueryContextMap already exists for " + nameServerAddr;
104 
105             InetSocketAddress extraAddress = null;
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                     extraAddress = new InetSocketAddress(NetUtil.LOCALHOST6, port);
111                 } else {
112                     extraAddress = new InetSocketAddress(toCompactAddress(a4), port);
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                     extraAddress = new InetSocketAddress(NetUtil.LOCALHOST4, port);
119                 } else if (a6.isIPv4CompatibleAddress()) {
120                     extraAddress = new InetSocketAddress(toIPv4Address(a6), port);
121                 }
122             }
123             if (extraAddress != null) {
124                 old = map.put(extraAddress, newContexts);
125                 // Assert that we didn't replace an existing mapping.
126                 assert old == null : "DnsQueryContextMap already exists for " + extraAddress;
127             }
128 
129             return newContexts;
130         }
131     }
132 
133     private static Inet6Address toCompactAddress(Inet4Address a4) {
134         byte[] b4 = a4.getAddress();
135         byte[] b6 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, b4[0], b4[1], b4[2], b4[3] };
136         try {
137             return (Inet6Address) InetAddress.getByAddress(b6);
138         } catch (UnknownHostException e) {
139             throw new Error(e);
140         }
141     }
142 
143     private static Inet4Address toIPv4Address(Inet6Address a6) {
144         assert a6.isIPv4CompatibleAddress();
145 
146         byte[] b6 = a6.getAddress();
147         byte[] b4 = { b6[12], b6[13], b6[14], b6[15] };
148         try {
149             return (Inet4Address) InetAddress.getByAddress(b4);
150         } catch (UnknownHostException e) {
151             throw new Error(e);
152         }
153     }
154 
155     private static final class DnsQueryContextMap {
156 
157         private final DnsQueryIdSpace idSpace = new DnsQueryIdSpace();
158 
159         // We increment on every usage so start with -1, this will ensure we start with 0 as first id.
160         private final IntObjectMap<DnsQueryContext> map = new IntObjectHashMap<DnsQueryContext>();
161 
162         synchronized int add(DnsQueryContext ctx) {
163             int id = idSpace.nextId();
164             DnsQueryContext oldCtx = map.put(id, ctx);
165             assert oldCtx == null;
166             return id;
167         }
168 
169         synchronized DnsQueryContext get(int id) {
170             return map.get(id);
171         }
172 
173         synchronized DnsQueryContext remove(int id) {
174             idSpace.pushId(id);
175             return map.remove(id);
176         }
177     }
178 }