Skip to content

Commit

Permalink
Revamp DNS codec
Browse files Browse the repository at this point in the history
Motivation:

There are various known issues in netty-codec-dns:

- Message types are not interfaces, which can make it difficult for a
  user to implement his/her own message implementation.
- Some class names and field names do not match with the terms in the
  RFC.
- The support for decoding a DNS record was limited. A user had to
  encode and decode by him/herself.
- The separation of DnsHeader from DnsMessage was unnecessary, although
  it is fine conceptually.
- Buffer leak caused by DnsMessage was difficult to analyze, because the
  leak detector tracks down the underlying ByteBuf rather than the
  DnsMessage itself.
- DnsMessage assumes DNS-over-UDP.
- To send an EDNS message, a user have to create a new DNS record class
  instance unnecessarily.

Modifications:

- Make all message types interfaces and add default implementations
- Rename some classes, properties, and constants to match the RFCs
  - DnsResource -> DnsRecord
  - DnsType -> DnsRecordType
  - and many more
- Remove DnsClass and use an integer to support EDNS better
- Add DnsRecordEncoder/DnsRecordDecoder and their default
  implementations
  - DnsRecord does not require RDATA to be ByteBuf anymore.
  - Add DnsRawRecord as the catch-all record type
- Merge DnsHeader into DnsMessage
- Make ResourceLeakDetector track AbstractDnsMessage
- Remove DnsMessage.sender/recipient properties
  - Wrap DnsMessage with AddressedEnvelope
  - Add DatagramDnsQuest and DatagramDnsResponse for ease of use
  - Rename DnsQueryEncoder to DatagramDnsQueryEncoder
  - Rename DnsResponseDecoder to DatagramDnsResponseDecoder
- Miscellaneous changes
  - Add StringUtil.TAB

Result:

- Cleaner APi
- Can support DNS-over-TCP more easily in the future
- Reduced memory footprint in the default DnsQuery/Response
  implementations
- Better leak tracking for DnsMessages
- Possibility to introduce new DnsRecord types in the future and provide
  full record encoder/decoder implementation.
- No unnecessary instantiation for an EDNS pseudo resource record
  • Loading branch information
trustin committed May 1, 2015
1 parent b122fae commit c27c487
Show file tree
Hide file tree
Showing 44 changed files with 3,126 additions and 1,854 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* 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.util.internal.StringUtil;

import static io.netty.util.internal.ObjectUtil.checkNotNull;

/**
* A skeletal implementation of {@link DnsRecord}.
*/
public abstract class AbstractDnsRecord implements DnsRecord {

private final String name;
private final DnsRecordType type;
private final short dnsClass;
private final long timeToLive;
private int hashCode;

/**
* Creates a new {@link #CLASS_IN IN-class} record.
*
* @param name the domain name
* @param type the type of the record
* @param timeToLive the TTL value of the record
*/
protected AbstractDnsRecord(String name, DnsRecordType type, long timeToLive) {
this(name, type, CLASS_IN, timeToLive);
}

/**
* Creates a new record.
*
* @param name the domain name
* @param type the type of the record
* @param dnsClass the class of the record, usually one of the following:
* <ul>
* <li>{@link #CLASS_IN}</li>
* <li>{@link #CLASS_CSNET}</li>
* <li>{@link #CLASS_CHAOS}</li>
* <li>{@link #CLASS_HESIOD}</li>
* <li>{@link #CLASS_NONE}</li>
* <li>{@link #CLASS_ANY}</li>
* </ul>
* @param timeToLive the TTL value of the record
*/
protected AbstractDnsRecord(String name, DnsRecordType type, int dnsClass, long timeToLive) {
if (timeToLive < 0) {
throw new IllegalArgumentException("timeToLive: " + timeToLive + " (expected: >= 0)");
}
this.name = checkNotNull(name, "name");
this.type = checkNotNull(type, "type");
this.dnsClass = (short) dnsClass;
this.timeToLive = timeToLive;
}

@Override
public String name() {
return name;
}

@Override
public DnsRecordType type() {
return type;
}

@Override
public int dnsClass() {
return dnsClass & 0xFFFF;
}

@Override
public long timeToLive() {
return timeToLive;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}

if (!(obj instanceof DnsRecord)) {
return false;
}

final DnsRecord that = (DnsRecord) obj;
final int hashCode = this.hashCode;
if (hashCode != 0 && hashCode != that.hashCode()) {
return false;
}

return type().intValue() == that.type().intValue() &&
dnsClass() == that.dnsClass() &&
name().equals(that.name());
}

@Override
public int hashCode() {
final int hashCode = this.hashCode;
if (hashCode != 0) {
return hashCode;
}

return this.hashCode = name.hashCode() * 31 + type().intValue() * 31 + dnsClass();
}

@Override
public String toString() {
StringBuilder buf = new StringBuilder(64);

buf.append(StringUtil.simpleClassName(this))
.append('(')
.append(name())
.append(' ')
.append(timeToLive())
.append(' ');

DnsMessageUtil.appendRecordClass(buf, dnsClass())
.append(' ')
.append(type().name())
.append(')');

return buf.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* 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.channel.AddressedEnvelope;

import java.net.InetSocketAddress;
import java.net.SocketAddress;

/**
* A {@link DnsQuery} implementation for UDP/IP.
*/
public class DatagramDnsQuery extends DefaultDnsQuery
implements AddressedEnvelope<DatagramDnsQuery, InetSocketAddress> {

private final InetSocketAddress sender;
private final InetSocketAddress recipient;

/**
* Creates a new instance with the {@link DnsOpCode#QUERY} {@code opCode}.
*
* @param sender the address of the sender
* @param recipient the address of the recipient
* @param id the {@code ID} of the DNS query
*/
public DatagramDnsQuery(
InetSocketAddress sender, InetSocketAddress recipient, int id) {
this(sender, recipient, id, DnsOpCode.QUERY);
}

/**
* Creates a new instance.
*
* @param sender the address of the sender
* @param recipient the address of the recipient
* @param id the {@code ID} of the DNS query
* @param opCode the {@code opCode} of the DNS query
*/
public DatagramDnsQuery(
InetSocketAddress sender, InetSocketAddress recipient, int id, DnsOpCode opCode) {
super(id, opCode);

if (recipient == null && sender == null) {
throw new NullPointerException("recipient and sender");
}

this.sender = sender;
this.recipient = recipient;
}

@Override
public DatagramDnsQuery content() {
return this;
}

@Override
public InetSocketAddress sender() {
return sender;
}

@Override
public InetSocketAddress recipient() {
return recipient;
}

@Override
public DatagramDnsQuery setId(int id) {
return (DatagramDnsQuery) super.setId(id);
}

@Override
public DatagramDnsQuery setOpCode(DnsOpCode opCode) {
return (DatagramDnsQuery) super.setOpCode(opCode);
}

@Override
public DatagramDnsQuery setRecursionDesired(boolean recursionDesired) {
return (DatagramDnsQuery) super.setRecursionDesired(recursionDesired);
}

@Override
public DatagramDnsQuery setZ(int z) {
return (DatagramDnsQuery) super.setZ(z);
}

@Override
public DatagramDnsQuery setRecord(DnsSection section, DnsRecord record) {
return (DatagramDnsQuery) super.setRecord(section, record);
}

@Override
public DatagramDnsQuery addRecord(DnsSection section, DnsRecord record) {
return (DatagramDnsQuery) super.addRecord(section, record);
}

@Override
public DatagramDnsQuery addRecord(DnsSection section, int index, DnsRecord record) {
return (DatagramDnsQuery) super.addRecord(section, index, record);
}

@Override
public DatagramDnsQuery clear(DnsSection section) {
return (DatagramDnsQuery) super.clear(section);
}

@Override
public DatagramDnsQuery clear() {
return (DatagramDnsQuery) super.clear();
}

@Override
public DatagramDnsQuery touch() {
return (DatagramDnsQuery) super.touch();
}

@Override
public DatagramDnsQuery touch(Object hint) {
return (DatagramDnsQuery) super.touch(hint);
}

@Override
public DatagramDnsQuery retain() {
return (DatagramDnsQuery) super.retain();
}

@Override
public DatagramDnsQuery retain(int increment) {
return (DatagramDnsQuery) super.retain(increment);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}

if (!super.equals(obj)) {
return false;
}

if (!(obj instanceof AddressedEnvelope)) {
return false;
}

@SuppressWarnings("unchecked")
final AddressedEnvelope<?, SocketAddress> that = (AddressedEnvelope<?, SocketAddress>) obj;
if (sender() == null) {
if (that.sender() != null) {
return false;
}
} else if (!sender().equals(that.sender())) {
return false;
}

if (recipient() == null) {
if (that.recipient() != null) {
return false;
}
} else if (!recipient().equals(that.recipient())) {
return false;
}

return true;
}

@Override
public int hashCode() {
int hashCode = super.hashCode();
if (sender() != null) {
hashCode = hashCode * 31 + sender().hashCode();
}
if (recipient() != null) {
hashCode = hashCode * 31 + recipient().hashCode();
}
return hashCode;
}
}
Loading

0 comments on commit c27c487

Please sign in to comment.