diff --git a/src/main/java/io/vertx/core/dns/impl/DnsClientImpl.java b/src/main/java/io/vertx/core/dns/impl/DnsClientImpl.java index eeda3dc4274..1cd5fc8913c 100644 --- a/src/main/java/io/vertx/core/dns/impl/DnsClientImpl.java +++ b/src/main/java/io/vertx/core/dns/impl/DnsClientImpl.java @@ -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; @@ -261,7 +262,14 @@ void handle(DnsResponse msg) { List 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); } diff --git a/src/main/java/io/vertx/core/dns/impl/decoder/RecordDecoder.java b/src/main/java/io/vertx/core/dns/impl/decoder/RecordDecoder.java index 741badbff8b..0edfd09763e 100644 --- a/src/main/java/io/vertx/core/dns/impl/decoder/RecordDecoder.java +++ b/src/main/java/io/vertx/core/dns/impl/decoder/RecordDecoder.java @@ -222,7 +222,7 @@ public static T decode(DnsRecord record) { DnsRecordType type = record.type(); Function 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 { diff --git a/src/test/java/io/vertx/core/dns/DNSTest.java b/src/test/java/io/vertx/core/dns/DNSTest.java index e3682288f20..a5752c75168 100644 --- a/src/test/java/io/vertx/core/dns/DNSTest.java +++ b/src/test/java/io/vertx/core/dns/DNSTest.java @@ -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); diff --git a/src/test/java/io/vertx/test/fakedns/DnameRecordEncoder.java b/src/test/java/io/vertx/test/fakedns/DnameRecordEncoder.java new file mode 100644 index 00000000000..cb7c915ee5e --- /dev/null +++ b/src/test/java/io/vertx/test/fakedns/DnameRecordEncoder.java @@ -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 ); + } +} diff --git a/src/test/java/io/vertx/test/fakedns/DnsMessageEncoder.java b/src/test/java/io/vertx/test/fakedns/DnsMessageEncoder.java new file mode 100644 index 00000000000..c045e886a9f --- /dev/null +++ b/src/test/java/io/vertx/test/fakedns/DnsMessageEncoder.java @@ -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 encode(ByteBuffer, DnsMessage) method which will + * write the message to the outgoing ByteBuffer according to the DnsMessage + * encoding in RFC-1035. + * + * @author Apache Directory Project + * @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 DEFAULT_ENCODERS; + + static + { + Map map = new HashMap(); + + 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 questions ) + { + if ( questions == null ) + { + return; + } + + QuestionRecordEncoder encoder = new QuestionRecordEncoder(); + + Iterator it = questions.iterator(); + + while ( it.hasNext() ) + { + QuestionRecord question = it.next(); + encoder.put( byteBuffer, question ); + } + } + + + private void putResourceRecords( IoBuffer byteBuffer, List records ) + { + if ( records == null ) + { + return; + } + + Iterator 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 ); + } +} diff --git a/src/test/java/io/vertx/test/fakedns/FakeDNSServer.java b/src/test/java/io/vertx/test/fakedns/FakeDNSServer.java index f503c1ba356..fac51953140 100644 --- a/src/test/java/io/vertx/test/fakedns/FakeDNSServer.java +++ b/src/test/java/io/vertx/test/fakedns/FakeDNSServer.java @@ -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; @@ -263,6 +262,24 @@ public Set getRecords(QuestionRecord questionRecord) throws org. }); } + public FakeDNSServer testResolveDNAME(final String dname) { + return store(new RecordStore() { + @Override + public Set getRecords(QuestionRecord questionRecord) throws org.apache.directory.server.dns.DnsException { + Set 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