001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 021package org.apache.directory.server.dns.io.decoder; 022 023 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.EnumMap; 028import java.util.List; 029import java.util.Map; 030 031import org.apache.directory.server.dns.messages.DnsMessage; 032import org.apache.directory.server.dns.messages.DnsMessageModifier; 033import org.apache.directory.server.dns.messages.MessageType; 034import org.apache.directory.server.dns.messages.OpCode; 035import org.apache.directory.server.dns.messages.QuestionRecord; 036import org.apache.directory.server.dns.messages.RecordClass; 037import org.apache.directory.server.dns.messages.RecordType; 038import org.apache.directory.server.dns.messages.ResourceRecord; 039import org.apache.directory.server.dns.messages.ResourceRecordImpl; 040import org.apache.directory.server.dns.messages.ResponseCode; 041import org.apache.directory.server.i18n.I18n; 042import org.apache.mina.core.buffer.IoBuffer; 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045 046 047/** 048 * A decoder for DNS messages. The primary usage of the DnsMessageDecoder is by 049 * calling the <code>decode(ByteBuffer)</code> method which will read the 050 * message from the incoming ByteBuffer and build a <code>DnsMessage</code> 051 * from it according to the DnsMessage encoding in RFC-1035. 052 * 053 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 054 */ 055public class DnsMessageDecoder 056{ 057 058 private final Logger logger = LoggerFactory.getLogger( DnsMessageDecoder.class ); 059 060 /** 061 * A Hashed Adapter mapping record types to their encoders. 062 */ 063 private static final Map<RecordType, RecordDecoder> DEFAULT_DECODERS; 064 065 static 066 { 067 EnumMap<RecordType, RecordDecoder> map = new EnumMap<>( RecordType.class ); 068 069 map.put( RecordType.A, new AddressRecordDecoder() ); 070 map.put( RecordType.NS, new NameServerRecordDecoder() ); 071 map.put( RecordType.MX, new MailExchangeRecordDecoder() ); 072 map.put( RecordType.AAAA, new IPv6RecordDecoder() ); 073 074 DEFAULT_DECODERS = Collections.unmodifiableMap( map ); 075 } 076 077 078 /** 079 * Decode the {@link IoBuffer} into a {@link DnsMessage}. 080 * 081 * @param in 082 * @return The {@link DnsMessage}. 083 * @throws IOException 084 */ 085 public DnsMessage decode( IoBuffer in ) throws IOException 086 { 087 DnsMessageModifier modifier = new DnsMessageModifier(); 088 089 modifier.setTransactionId( in.getUnsignedShort() ); 090 091 byte header = in.get(); 092 modifier.setMessageType( decodeMessageType( header ) ); 093 modifier.setOpCode( decodeOpCode( header ) ); 094 modifier.setAuthoritativeAnswer( decodeAuthoritativeAnswer( header ) ); 095 modifier.setTruncated( decodeTruncated( header ) ); 096 modifier.setRecursionDesired( decodeRecursionDesired( header ) ); 097 098 header = in.get(); 099 modifier.setRecursionAvailable( decodeRecursionAvailable( header ) ); 100 modifier.setResponseCode( decodeResponseCode( header ) ); 101 102 short questionCount = in.getShort(); 103 short answerCount = in.getShort(); 104 short authorityCount = in.getShort(); 105 short additionalCount = in.getShort(); 106 107 logger.debug( "decoding {} question records", questionCount ); 108 modifier.setQuestionRecords( getQuestions( in, questionCount ) ); 109 110 logger.debug( "decoding {} answer records", answerCount ); 111 modifier.setAnswerRecords( getRecords( in, answerCount ) ); 112 113 logger.debug( "decoding {} authority records", authorityCount ); 114 modifier.setAuthorityRecords( getRecords( in, authorityCount ) ); 115 116 logger.debug( "decoding {} additional records", additionalCount ); 117 modifier.setAdditionalRecords( getRecords( in, additionalCount ) ); 118 119 return modifier.getDnsMessage(); 120 } 121 122 123 private List<ResourceRecord> getRecords( IoBuffer byteBuffer, short recordCount ) throws IOException 124 { 125 List<ResourceRecord> records = new ArrayList<>( recordCount ); 126 127 for ( int ii = 0; ii < recordCount; ii++ ) 128 { 129 String domainName = getDomainName( byteBuffer ); 130 RecordType recordType = RecordType.convert( byteBuffer.getShort() ); 131 RecordClass recordClass = RecordClass.convert( byteBuffer.getShort() ); 132 133 int timeToLive = byteBuffer.getInt(); 134 short dataLength = byteBuffer.getShort(); 135 136 Map<String, Object> attributes = decode( byteBuffer, recordType, dataLength ); 137 records.add( new ResourceRecordImpl( domainName, recordType, recordClass, timeToLive, attributes ) ); 138 } 139 140 return records; 141 } 142 143 144 private Map<String, Object> decode( IoBuffer byteBuffer, RecordType type, short length ) throws IOException 145 { 146 RecordDecoder recordDecoder = DEFAULT_DECODERS.get( type ); 147 148 if ( recordDecoder == null ) 149 { 150 throw new IllegalArgumentException( I18n.err( I18n.ERR_600, type ) ); 151 } 152 153 return recordDecoder.decode( byteBuffer, length ); 154 } 155 156 157 private List<QuestionRecord> getQuestions( IoBuffer byteBuffer, short questionCount ) 158 { 159 List<QuestionRecord> questions = new ArrayList<>( questionCount ); 160 161 for ( int ii = 0; ii < questionCount; ii++ ) 162 { 163 String domainName = getDomainName( byteBuffer ); 164 165 RecordType recordType = RecordType.convert( byteBuffer.getShort() ); 166 RecordClass recordClass = RecordClass.convert( byteBuffer.getShort() ); 167 168 questions.add( new QuestionRecord( domainName, recordType, recordClass ) ); 169 } 170 171 return questions; 172 } 173 174 175 static String getDomainName( IoBuffer byteBuffer ) 176 { 177 StringBuilder domainName = new StringBuilder(); 178 recurseDomainName( byteBuffer, domainName ); 179 180 return domainName.toString(); 181 } 182 183 184 static void recurseDomainName( IoBuffer byteBuffer, StringBuilder domainName ) 185 { 186 int length = byteBuffer.getUnsigned(); 187 188 if ( isOffset( length ) ) 189 { 190 int position = byteBuffer.getUnsigned(); 191 int offset = length & ~( 0xc0 ) << 8; 192 int originalPosition = byteBuffer.position(); 193 byteBuffer.position( position + offset ); 194 195 recurseDomainName( byteBuffer, domainName ); 196 197 byteBuffer.position( originalPosition ); 198 } 199 else if ( isLabel( length ) ) 200 { 201 int labelLength = length; 202 getLabel( byteBuffer, domainName, labelLength ); 203 recurseDomainName( byteBuffer, domainName ); 204 } 205 } 206 207 208 static boolean isOffset( int length ) 209 { 210 return ( ( length & 0xc0 ) == 0xc0 ); 211 } 212 213 214 static boolean isLabel( int length ) 215 { 216 return ( length != 0 && ( length & 0xc0 ) == 0 ); 217 } 218 219 220 static void getLabel( IoBuffer byteBuffer, StringBuilder domainName, int labelLength ) 221 { 222 for ( int jj = 0; jj < labelLength; jj++ ) 223 { 224 char character = ( char ) byteBuffer.get(); 225 domainName.append( character ); 226 } 227 228 if ( byteBuffer.get( byteBuffer.position() ) != 0 ) 229 { 230 domainName.append( "." ); 231 } 232 } 233 234 235 private MessageType decodeMessageType( byte header ) 236 { 237 return MessageType.convert( ( byte ) ( ( header & 0x80 ) >>> 7 ) ); 238 } 239 240 241 private OpCode decodeOpCode( byte header ) 242 { 243 return OpCode.convert( ( byte ) ( ( header & 0x78 ) >>> 3 ) ); 244 } 245 246 247 private boolean decodeAuthoritativeAnswer( byte header ) 248 { 249 return ( ( header & 0x04 ) >>> 2 ) == 1; 250 } 251 252 253 private boolean decodeTruncated( byte header ) 254 { 255 return ( ( header & 0x02 ) >>> 1 ) == 1; 256 } 257 258 259 private boolean decodeRecursionDesired( byte header ) 260 { 261 return ( header & 0x01 ) == 1; 262 } 263 264 265 private boolean decodeRecursionAvailable( byte header ) 266 { 267 return ( ( header & 0x80 ) >>> 7 ) == 1; 268 } 269 270 271 private ResponseCode decodeResponseCode( byte header ) 272 { 273 return ResponseCode.convert( ( byte ) ( header & 0x0F ) ); 274 } 275}