2015-11-08 04:59:01 +01:00
|
|
|
/*
|
|
|
|
* Copyright 2015 The Netty Project
|
|
|
|
*
|
|
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
|
|
* with the License. You may obtain a copy of the License at:
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
* License for the specific language governing permissions and limitations
|
|
|
|
* under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package io.netty.resolver.dns;
|
|
|
|
|
|
|
|
import io.netty.util.NetUtil;
|
|
|
|
import io.netty.util.collection.IntObjectHashMap;
|
|
|
|
import io.netty.util.collection.IntObjectMap;
|
|
|
|
|
|
|
|
import java.net.Inet4Address;
|
|
|
|
import java.net.Inet6Address;
|
|
|
|
import java.net.InetAddress;
|
|
|
|
import java.net.InetSocketAddress;
|
|
|
|
import java.net.UnknownHostException;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Map;
|
2019-01-22 20:14:28 +01:00
|
|
|
import java.util.concurrent.ThreadLocalRandom;
|
2015-11-08 04:59:01 +01:00
|
|
|
|
|
|
|
final class DnsQueryContextManager {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A map whose key is the DNS server address and value is the map of the DNS query ID and its corresponding
|
|
|
|
* {@link DnsQueryContext}.
|
|
|
|
*/
|
2019-01-22 20:14:28 +01:00
|
|
|
final Map<InetSocketAddress, IntObjectMap<DnsQueryContext>> map = new HashMap<>();
|
2015-11-08 04:59:01 +01:00
|
|
|
|
|
|
|
int add(DnsQueryContext qCtx) {
|
|
|
|
final IntObjectMap<DnsQueryContext> contexts = getOrCreateContextMap(qCtx.nameServerAddr());
|
|
|
|
|
2019-01-22 20:14:28 +01:00
|
|
|
int id = ThreadLocalRandom.current().nextInt(65536 - 1) + 1;
|
2015-11-08 04:59:01 +01:00
|
|
|
final int maxTries = 65535 << 1;
|
|
|
|
int tries = 0;
|
|
|
|
|
|
|
|
synchronized (contexts) {
|
|
|
|
for (;;) {
|
|
|
|
if (!contexts.containsKey(id)) {
|
|
|
|
contexts.put(id, qCtx);
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
id = id + 1 & 0xFFFF;
|
|
|
|
|
|
|
|
if (++tries >= maxTries) {
|
|
|
|
throw new IllegalStateException("query ID space exhausted: " + qCtx.question());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DnsQueryContext get(InetSocketAddress nameServerAddr, int id) {
|
|
|
|
final IntObjectMap<DnsQueryContext> contexts = getContextMap(nameServerAddr);
|
|
|
|
final DnsQueryContext qCtx;
|
|
|
|
if (contexts != null) {
|
|
|
|
synchronized (contexts) {
|
|
|
|
qCtx = contexts.get(id);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
qCtx = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return qCtx;
|
|
|
|
}
|
|
|
|
|
|
|
|
DnsQueryContext remove(InetSocketAddress nameServerAddr, int id) {
|
|
|
|
final IntObjectMap<DnsQueryContext> contexts = getContextMap(nameServerAddr);
|
|
|
|
if (contexts == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
synchronized (contexts) {
|
|
|
|
return contexts.remove(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private IntObjectMap<DnsQueryContext> getContextMap(InetSocketAddress nameServerAddr) {
|
|
|
|
synchronized (map) {
|
|
|
|
return map.get(nameServerAddr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private IntObjectMap<DnsQueryContext> getOrCreateContextMap(InetSocketAddress nameServerAddr) {
|
|
|
|
synchronized (map) {
|
|
|
|
final IntObjectMap<DnsQueryContext> contexts = map.get(nameServerAddr);
|
|
|
|
if (contexts != null) {
|
|
|
|
return contexts;
|
|
|
|
}
|
|
|
|
|
|
|
|
final IntObjectMap<DnsQueryContext> newContexts = new IntObjectHashMap<DnsQueryContext>();
|
|
|
|
final InetAddress a = nameServerAddr.getAddress();
|
|
|
|
final int port = nameServerAddr.getPort();
|
|
|
|
map.put(nameServerAddr, newContexts);
|
|
|
|
|
|
|
|
if (a instanceof Inet4Address) {
|
|
|
|
// Also add the mapping for the IPv4-compatible IPv6 address.
|
|
|
|
final Inet4Address a4 = (Inet4Address) a;
|
|
|
|
if (a4.isLoopbackAddress()) {
|
|
|
|
map.put(new InetSocketAddress(NetUtil.LOCALHOST6, port), newContexts);
|
|
|
|
} else {
|
2017-04-19 22:37:03 +02:00
|
|
|
map.put(new InetSocketAddress(toCompactAddress(a4), port), newContexts);
|
2015-11-08 04:59:01 +01:00
|
|
|
}
|
|
|
|
} else if (a instanceof Inet6Address) {
|
|
|
|
// Also add the mapping for the IPv4 address if this IPv6 address is compatible.
|
|
|
|
final Inet6Address a6 = (Inet6Address) a;
|
|
|
|
if (a6.isLoopbackAddress()) {
|
|
|
|
map.put(new InetSocketAddress(NetUtil.LOCALHOST4, port), newContexts);
|
|
|
|
} else if (a6.isIPv4CompatibleAddress()) {
|
|
|
|
map.put(new InetSocketAddress(toIPv4Address(a6), port), newContexts);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return newContexts;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-19 22:37:03 +02:00
|
|
|
private static Inet6Address toCompactAddress(Inet4Address a4) {
|
2015-11-08 04:59:01 +01:00
|
|
|
byte[] b4 = a4.getAddress();
|
|
|
|
byte[] b6 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, b4[0], b4[1], b4[2], b4[3] };
|
|
|
|
try {
|
|
|
|
return (Inet6Address) InetAddress.getByAddress(b6);
|
|
|
|
} catch (UnknownHostException e) {
|
|
|
|
throw new Error(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Inet4Address toIPv4Address(Inet6Address a6) {
|
|
|
|
byte[] b6 = a6.getAddress();
|
|
|
|
byte[] b4 = { b6[12], b6[13], b6[14], b6[15] };
|
|
|
|
try {
|
|
|
|
return (Inet4Address) InetAddress.getByAddress(b4);
|
|
|
|
} catch (UnknownHostException e) {
|
|
|
|
throw new Error(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|