Skip to content

Commit

Permalink
Merge pull request #4908 from mnylen/fix-dns-client-illegal-state-exc…
Browse files Browse the repository at this point in the history
…eption

Fix IllegalStateException in DnsClient when DNS server responds with DNAME record
  • Loading branch information
vietj authored Nov 6, 2023
2 parents 84ecbb3 + 419f897 commit 9e02ae0
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 3 deletions.
10 changes: 9 additions & 1 deletion src/main/java/io/vertx/core/dns/impl/DnsClientImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.netty.channel.*;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.dns.*;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.collection.LongObjectHashMap;
Expand Down Expand Up @@ -261,7 +262,14 @@ void handle(DnsResponse msg) {
List<T> records = new ArrayList<>(count);
for (int idx = 0;idx < count;idx++) {
DnsRecord a = msg.recordAt(DnsSection.ANSWER, idx);
T record = RecordDecoder.decode(a);
T record;
try {
record = RecordDecoder.decode(a);
} catch (DecoderException e) {
fail(e);
return;
}

if (isRequestedType(a.type(), types)) {
records.add(record);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ public static <T> T decode(DnsRecord record) {
DnsRecordType type = record.type();
Function<DnsRecord, ?> decoder = decoders.get(type);
if (decoder == null) {
throw new IllegalStateException("Unsupported resource record type [id: " + type + "].");
throw new DecoderException("DNS record decoding error occurred: Unsupported resource record type [id: " + type + "].");
}
T result = null;
try {
Expand Down
13 changes: 13 additions & 0 deletions src/test/java/io/vertx/core/dns/DNSTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,19 @@ public void testLookup4CNAME() throws Exception {
await();
}

@Test
public void testResolveMXWhenDNSRepliesWithDNAMERecord() throws Exception {
final DnsClient dns = prepareDns();
dnsServer.testResolveDNAME("mail.vertx.io");

dns.resolveMX("vertx.io")
.onComplete(ar -> {
assertTrue(ar.failed());
testComplete();
});
await();
}

private TestLoggerFactory testLogging(DnsClientOptions options) {
final String ip = "10.0.0.1";
dnsServer.testResolveA(ip);
Expand Down
15 changes: 15 additions & 0 deletions src/test/java/io/vertx/test/fakedns/DnameRecordEncoder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.vertx.test.fakedns;

import org.apache.directory.server.dns.io.encoder.ResourceRecordEncoder;
import org.apache.directory.server.dns.messages.ResourceRecord;
import org.apache.directory.server.dns.store.DnsAttribute;
import org.apache.mina.core.buffer.IoBuffer;

public class DnameRecordEncoder extends ResourceRecordEncoder {

protected void putResourceRecordData(IoBuffer byteBuffer, ResourceRecord record )
{
String domainName = record.get( DnsAttribute.DOMAIN_NAME );
putDomainName( byteBuffer, domainName );
}
}
233 changes: 233 additions & 0 deletions src/test/java/io/vertx/test/fakedns/DnsMessageEncoder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package io.vertx.test.fakedns;

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.
*
*/


import java.io.IOException;
import java.util.*;

import org.apache.directory.server.dns.io.encoder.*;
import org.apache.directory.server.dns.messages.DnsMessage;
import org.apache.directory.server.dns.messages.MessageType;
import org.apache.directory.server.dns.messages.OpCode;
import org.apache.directory.server.dns.messages.QuestionRecord;
import org.apache.directory.server.dns.messages.RecordType;
import org.apache.directory.server.dns.messages.ResourceRecord;
import org.apache.directory.server.dns.messages.ResponseCode;
import org.apache.directory.server.i18n.I18n;
import org.apache.mina.core.buffer.IoBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* An encoder for DNS messages. The primary usage of the DnsMessageEncoder is
* to call the <code>encode(ByteBuffer, DnsMessage)</code> method which will
* write the message to the outgoing ByteBuffer according to the DnsMessage
* encoding in RFC-1035.
*
* @author <a href="mailto:[email protected]">Apache Directory Project</a>
* @version $Rev$, $Date$
*/
public class DnsMessageEncoder
{
/** the log for this class */
private static final Logger log = LoggerFactory.getLogger( DnsMessageEncoder.class );

/**
* A Hashed Adapter mapping record types to their encoders.
*/
private static final Map<RecordType, RecordEncoder> DEFAULT_ENCODERS;

static
{
Map<RecordType, RecordEncoder> map = new HashMap<RecordType, RecordEncoder>();

map.put( RecordType.SOA, new StartOfAuthorityRecordEncoder() );
map.put( RecordType.A, new AddressRecordEncoder() );
map.put( RecordType.NS, new NameServerRecordEncoder() );
map.put( RecordType.CNAME, new CanonicalNameRecordEncoder() );
map.put( RecordType.PTR, new PointerRecordEncoder() );
map.put( RecordType.MX, new MailExchangeRecordEncoder() );
map.put( RecordType.SRV, new ServerSelectionRecordEncoder() );
map.put( RecordType.TXT, new TextRecordEncoder() );
map.put( RecordType.DNAME, new DnameRecordEncoder());

DEFAULT_ENCODERS = Collections.unmodifiableMap( map );
}


/**
* Encodes the {@link DnsMessage} into the {@link ByteBuffer}.
*
* @param byteBuffer
* @param message
*/
public void encode( IoBuffer byteBuffer, DnsMessage message )
{
byteBuffer.putShort( ( short ) message.getTransactionId() );

byte header = ( byte ) 0x00;
header |= encodeMessageType( message.getMessageType() );
header |= encodeOpCode( message.getOpCode() );
header |= encodeAuthoritativeAnswer( message.isAuthoritativeAnswer() );
header |= encodeTruncated( message.isTruncated() );
header |= encodeRecursionDesired( message.isRecursionDesired() );
byteBuffer.put( header );

header = ( byte ) 0x00;
header |= encodeRecursionAvailable( message.isRecursionAvailable() );
header |= encodeResponseCode( message.getResponseCode() );
byteBuffer.put( header );

byteBuffer
.putShort( ( short ) ( message.getQuestionRecords() != null ? message.getQuestionRecords().size() : 0 ) );
byteBuffer.putShort( ( short ) ( message.getAnswerRecords() != null ? message.getAnswerRecords().size() : 0 ) );
byteBuffer.putShort( ( short ) ( message.getAuthorityRecords() != null ? message.getAuthorityRecords().size()
: 0 ) );
byteBuffer.putShort( ( short ) ( message.getAdditionalRecords() != null ? message.getAdditionalRecords().size()
: 0 ) );

putQuestionRecords( byteBuffer, message.getQuestionRecords() );
putResourceRecords( byteBuffer, message.getAnswerRecords() );
putResourceRecords( byteBuffer, message.getAuthorityRecords() );
putResourceRecords( byteBuffer, message.getAdditionalRecords() );
}


private void putQuestionRecords( IoBuffer byteBuffer, List<QuestionRecord> questions )
{
if ( questions == null )
{
return;
}

QuestionRecordEncoder encoder = new QuestionRecordEncoder();

Iterator<QuestionRecord> it = questions.iterator();

while ( it.hasNext() )
{
QuestionRecord question = it.next();
encoder.put( byteBuffer, question );
}
}


private void putResourceRecords( IoBuffer byteBuffer, List<ResourceRecord> records )
{
if ( records == null )
{
return;
}

Iterator<ResourceRecord> it = records.iterator();

while ( it.hasNext() )
{
ResourceRecord record = it.next();

try
{
put( byteBuffer, record );
}
catch ( IOException ioe )
{
log.error( ioe.getLocalizedMessage(), ioe );
}
}
}


private void put( IoBuffer byteBuffer, ResourceRecord record ) throws IOException
{
RecordType type = record.getRecordType();

RecordEncoder encoder = DEFAULT_ENCODERS.get( type );

if ( encoder == null )
{
throw new IOException( I18n.err( I18n.ERR_597, type ) );
}

encoder.put( byteBuffer, record );
}


private byte encodeMessageType( MessageType messageType )
{
byte oneBit = ( byte ) ( messageType.convert() & 0x01 );
return ( byte ) ( oneBit << 7 );
}


private byte encodeOpCode( OpCode opCode )
{
byte fourBits = ( byte ) ( opCode.convert() & 0x0F );
return ( byte ) ( fourBits << 3 );
}


private byte encodeAuthoritativeAnswer( boolean authoritative )
{
if ( authoritative )
{
return ( byte ) ( ( byte ) 0x01 << 2 );
}
return ( byte ) 0;
}


private byte encodeTruncated( boolean truncated )
{
if ( truncated )
{
return ( byte ) ( ( byte ) 0x01 << 1 );
}
return 0;
}


private byte encodeRecursionDesired( boolean recursionDesired )
{
if ( recursionDesired )
{
return ( byte ) 0x01;
}
return 0;
}


private byte encodeRecursionAvailable( boolean recursionAvailable )
{
if ( recursionAvailable )
{
return ( byte ) ( ( byte ) 0x01 << 7 );
}
return 0;
}


private byte encodeResponseCode( ResponseCode responseCode )
{
return ( byte ) ( responseCode.convert() & 0x0F );
}
}
19 changes: 18 additions & 1 deletion src/test/java/io/vertx/test/fakedns/FakeDNSServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

import org.apache.directory.server.dns.DnsException;
import org.apache.directory.server.dns.DnsServer;
import org.apache.directory.server.dns.io.encoder.DnsMessageEncoder;
import org.apache.directory.server.dns.io.encoder.ResourceRecordEncoder;
import org.apache.directory.server.dns.messages.DnsMessage;
import org.apache.directory.server.dns.messages.DnsMessageModifier;
Expand Down Expand Up @@ -263,6 +262,24 @@ public Set<ResourceRecord> getRecords(QuestionRecord questionRecord) throws org.
});
}

public FakeDNSServer testResolveDNAME(final String dname) {
return store(new RecordStore() {
@Override
public Set<ResourceRecord> getRecords(QuestionRecord questionRecord) throws org.apache.directory.server.dns.DnsException {
Set<ResourceRecord> set = new HashSet<>();

ResourceRecordModifier rm = new ResourceRecordModifier();
rm.setDnsClass(RecordClass.IN);
rm.setDnsName("dns.vertx.io");
rm.setDnsTtl(100);
rm.setDnsType(RecordType.DNAME);
rm.put(DnsAttribute.DOMAIN_NAME, dname);
set.add(rm.getEntry());
return set;
}
});
}

public FakeDNSServer testLookup4(final String ip) {
return store(new RecordStore() {
@Override
Expand Down

0 comments on commit 9e02ae0

Please sign in to comment.