Related issues: - #3971 - #3973 - #3976 - #4035 Motivation: 1. Previously, DnsNameResolver.query() retried the request query by its own. It prevents a user from deciding when to retry or stop. It is also impossible to get the response object whose code is not NOERROR. 2. NameResolver does not have an operation that resolves a host name into multiple addresses, like InetAddress.getAllByName() Modifications: - Changes related with DnsNameResolver.query() - Make query() not retry - Move the retry logic to DnsNameResolver.resolve() instead. - Make query() fail the promise only when I/O error occurred or it failed to get a response - Add DnsNameResolverException and use it when query() fails so that the resolver can give more information about the failure - query() does not cache anymore. - Changes related with NameResolver.resolveAll() - Add NameResolver.resolveAll() - Add SimpleNameResolver.doResolveAll() - Changes related with DnsNameResolver.resolve() and resolveAll() - Make DnsNameResolveContext abstract so that DnsNameResolver can decide to get single or multiple addresses from it - Re-implement cache so that the cache works for resolve() and resolveAll() - Add 'traceEnabled' property to enable/disable trace information - Miscellaneous changes - Use ObjectUtil.checkNotNull() wherever possible - Add InternetProtocolFamily.addressType() to remove repetitive switch-case blocks in DnsNameResolver(Context) - Do not raise an exception when decoding a truncated DNS response Result: - Full control over query() - A user can now retrieve all addresses via (Dns)NameResolver.resolveAll() - DNS cache works only for resolve() and resolveAll() now.
140 lines
5.0 KiB
Java
140 lines
5.0 KiB
Java
/*
|
|
* 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.handler.codec.dns;
|
|
|
|
import io.netty.buffer.ByteBuf;
|
|
import io.netty.handler.codec.CorruptedFrameException;
|
|
import io.netty.util.CharsetUtil;
|
|
import io.netty.util.internal.StringUtil;
|
|
|
|
/**
|
|
* The default {@link DnsRecordDecoder} implementation.
|
|
*
|
|
* @see DefaultDnsRecordEncoder
|
|
*/
|
|
public class DefaultDnsRecordDecoder implements DnsRecordDecoder {
|
|
|
|
/**
|
|
* Creates a new instance.
|
|
*/
|
|
protected DefaultDnsRecordDecoder() { }
|
|
|
|
@Override
|
|
public final DnsQuestion decodeQuestion(ByteBuf in) throws Exception {
|
|
String name = decodeName(in);
|
|
DnsRecordType type = DnsRecordType.valueOf(in.readUnsignedShort());
|
|
int qClass = in.readUnsignedShort();
|
|
return new DefaultDnsQuestion(name, type, qClass);
|
|
}
|
|
|
|
@Override
|
|
public final <T extends DnsRecord> T decodeRecord(ByteBuf in) throws Exception {
|
|
final int startOffset = in.readerIndex();
|
|
final String name = decodeName(in);
|
|
|
|
final int endOffset = in.writerIndex();
|
|
if (endOffset - startOffset < 10) {
|
|
// Not enough data
|
|
in.readerIndex(startOffset);
|
|
return null;
|
|
}
|
|
|
|
final DnsRecordType type = DnsRecordType.valueOf(in.readUnsignedShort());
|
|
final int aClass = in.readUnsignedShort();
|
|
final long ttl = in.readUnsignedInt();
|
|
final int length = in.readUnsignedShort();
|
|
final int offset = in.readerIndex();
|
|
|
|
if (endOffset - offset < length) {
|
|
// Not enough data
|
|
in.readerIndex(startOffset);
|
|
return null;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
T record = (T) decodeRecord(name, type, aClass, ttl, in, offset, length);
|
|
in.readerIndex(offset + length);
|
|
return record;
|
|
}
|
|
|
|
/**
|
|
* Decodes a record from the information decoded so far by {@link #decodeRecord(ByteBuf)}.
|
|
*
|
|
* @param name the domain name of the record
|
|
* @param type the type of the record
|
|
* @param dnsClass the class of the record
|
|
* @param timeToLive the TTL of the record
|
|
* @param in the {@link ByteBuf} that contains the RDATA
|
|
* @param offset the start offset of the RDATA in {@code in}
|
|
* @param length the length of the RDATA
|
|
*
|
|
* @return a {@link DnsRawRecord}. Override this method to decode RDATA and return other record implementation.
|
|
*/
|
|
protected DnsRecord decodeRecord(
|
|
String name, DnsRecordType type, int dnsClass, long timeToLive,
|
|
ByteBuf in, int offset, int length) throws Exception {
|
|
|
|
return new DefaultDnsRawRecord(
|
|
name, type, dnsClass, timeToLive, in.duplicate().setIndex(offset, offset + length).retain());
|
|
}
|
|
|
|
/**
|
|
* Retrieves a domain name given a buffer containing a DNS packet. If the
|
|
* name contains a pointer, the position of the buffer will be set to
|
|
* directly after the pointer's index after the name has been read.
|
|
*
|
|
* @param in the byte buffer containing the DNS packet
|
|
* @return the domain name for an entry
|
|
*/
|
|
protected String decodeName(ByteBuf in) {
|
|
int position = -1;
|
|
int checked = 0;
|
|
final int end = in.writerIndex();
|
|
final StringBuilder name = new StringBuilder(in.readableBytes() << 1);
|
|
for (int len = in.readUnsignedByte(); in.isReadable() && len != 0; len = in.readUnsignedByte()) {
|
|
boolean pointer = (len & 0xc0) == 0xc0;
|
|
if (pointer) {
|
|
if (position == -1) {
|
|
position = in.readerIndex() + 1;
|
|
}
|
|
|
|
final int next = (len & 0x3f) << 8 | in.readUnsignedByte();
|
|
if (next >= end) {
|
|
throw new CorruptedFrameException("name has an out-of-range pointer");
|
|
}
|
|
in.readerIndex(next);
|
|
|
|
// check for loops
|
|
checked += 2;
|
|
if (checked >= end) {
|
|
throw new CorruptedFrameException("name contains a loop.");
|
|
}
|
|
} else {
|
|
name.append(in.toString(in.readerIndex(), len, CharsetUtil.UTF_8)).append('.');
|
|
in.skipBytes(len);
|
|
}
|
|
}
|
|
if (position != -1) {
|
|
in.readerIndex(position);
|
|
}
|
|
if (name.length() == 0) {
|
|
return StringUtil.EMPTY_STRING;
|
|
}
|
|
|
|
return name.substring(0, name.length() - 1);
|
|
}
|
|
}
|