/************************************************************************* * Copyright 2009-2014 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package com.eucalyptus.util.dns; import java.lang.reflect.Modifier; import java.net.InetAddress; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import org.springframework.core.OrderComparator; import org.xbill.DNS.Flags; import org.xbill.DNS.Message; import org.xbill.DNS.Name; import org.xbill.DNS.RRset; import org.xbill.DNS.Rcode; import org.xbill.DNS.Record; import org.xbill.DNS.SetResponse; import com.eucalyptus.bootstrap.Bootstrap; import com.eucalyptus.bootstrap.ServiceJarDiscovery; import com.eucalyptus.configurable.ConfigurableClass; import com.eucalyptus.configurable.ConfigurableField; import com.eucalyptus.util.Ordered; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ClassToInstanceMap; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.MutableClassToInstanceMap; @ConfigurableClass( root = "dns", description = "Configuration options controlling the behaviour of DNS features." ) public class DnsResolvers extends ServiceJarDiscovery { private static Logger LOG = Logger.getLogger( DnsResolvers.class ); @ConfigurableField( description = "Enable pluggable DNS resolvers. " + "Note: This must be 'true' for any pluggable resolver to work. " + "Also, each resolver may need to be separately enabled." + "See 'euca-describe-properties dns'.", initial = "true" ) public static Boolean enabled = Boolean.TRUE; private static final ClassToInstanceMap<DnsResolver> resolvers = MutableClassToInstanceMap.create( ); public enum RequestType implements Predicate<Record> { A( 1 ), NS( 2 ), MD( 3 ), MF( 4 ), CNAME( 5 ), SOA( 6 ), MB( 7 ), MG( 8 ), MR( 9 ), NULL( 10 ), WKS( 11 ), PTR( 12 ), HINFO( 13 ), MINFO( 14 ), MX( 15 ), TXT( 16 ), RP( 17 ), AFSDB( 18 ), X25( 19 ), ISDN( 20 ), RT( 21 ), NSAP( 22 ), NSAP_PTR( 23 ), SIG( 24 ), KEY( 25 ), PX( 26 ), GPOS( 27 ), AAAA( 28 ), LOC( 29 ), NXT( 30 ), EID( 31 ), NIMLOC( 32 ), SRV( 33 ), ATMA( 34 ), NAPTR( 35 ), KX( 36 ), CERT( 37 ), A6( 38 ), DNAME( 39 ), OPT( 41 ), APL( 42 ), DS( 43 ), SSHFP( 44 ), IPSECKEY( 45 ), RRSIG( 46 ), NSEC( 47 ), DNSKEY( 48 ), DHCID( 49 ), NSEC3( 50 ), NSEC3PARAM( 51 ), TLSA( 52 ), SPF( 99 ), TKEY( 249 ), TSIG( 250 ), IXFR( 251 ), AXFR( 252 ), MAILB( 253 ), MAILA( 254 ), ANY( 255 ), DLV( 32769 ); private static final Supplier<Map<Integer, RequestType>> backingMap = new Supplier( ) { @Override public Map<Integer, RequestType> get( ) { return new HashMap( ) { { for ( RequestType t : RequestType.values( ) ) { this.put( t.getType( ), t ); } } }; } }; private static final Supplier<Map<Integer, RequestType>> typeMap = Suppliers.memoize( backingMap ); private final int type; private RequestType( int type ) { this.type = type; } @Override public boolean apply( Record input ) { return input.getType( ) == this.type; } public static RequestType typeOf( int type ) { if ( !typeMap.get( ).containsKey( type ) ) { throw new IllegalArgumentException( "No RequestType with type=" + type ); } else { return typeMap.get( ).get( type ); } } public int getType( ) { return this.type; } } public enum ResponseSection { QUESTION, ANSWER, AUTHORITY, ADDITIONAL, ZONE { @Override public int section( ) { return 0; } }, PREREQ { @Override public int section( ) { return 1; } }, UPDATE { @Override public int section( ) { return 2; } }; public int section( ) { return this.ordinal( ); } } public enum ResponseType { SUCCESSFUL, CNAME, DELEGATION, DNAME, NXDOMAIN, NXRRSET, UNKNOWN; public static ResponseType lookup( SetResponse sr ) { if ( sr.isCNAME( ) ) { return CNAME; } else if ( sr.isDelegation( ) ) { return DELEGATION; } else if ( sr.isDNAME( ) ) { return DNAME; } else if ( sr.isNXDOMAIN( ) ) { return NXDOMAIN; } else if ( sr.isNXRRSET( ) ) { return NXRRSET; } else if ( sr.isSuccessful( ) ) { return SUCCESSFUL; } else { return UNKNOWN; } } } private static RRset createRRset( Record... records ) { RRset rrset = new RRset( ); for ( Record r : records ) { rrset.addRR( r ); } return rrset; } @SuppressWarnings( "unchecked" ) private static void addRRset( Name name, final Message response, Record[] records, final int section ) { final Multimap<RequestType, Record> rrsets = LinkedHashMultimap.create(); for ( Record r : records ) { RequestType type = RequestType.typeOf( r.getType( ) ); rrsets.get( type ).addAll( Collections2.filter( Arrays.asList( records ), type ) ); } Predicate<Record> checkNewRecord = new Predicate<Record>( ) { @Override public boolean apply( Record input ) { for ( int s = 1; s <= section; s++ ) { if ( response.findRecord( input, s ) ) { return false; } } return true; } }; if ( rrsets.containsKey( RequestType.CNAME ) ) { for ( Record cnames : Iterables.filter( rrsets.removeAll( RequestType.CNAME ), checkNewRecord ) ) { response.addRecord( cnames, section ); } } for ( Record sectionRecord : Iterables.filter( rrsets.values( ), checkNewRecord ) ) { response.addRecord( sectionRecord, section ); } } public static class DnsResponse { private final Multimap<ResponseSection, Record> sections = LinkedHashMultimap.create( ); private final Name name; private boolean recursive = false; private boolean nxdomain = false; private boolean refused = false; public static class Builder { private final DnsResponse response; Builder( Name name ) { this.response = new DnsResponse( name ); } public Builder withAuthority( List<Record> records ) { if ( records != null ) { this.response.sections.get( ResponseSection.AUTHORITY ).addAll( records ); } return this; } public Builder withAuthority( Record... records ) { if ( records != null ) { return withAuthority( Arrays.asList( records ) ); } else { return this; } } public Builder withAdditional( List<Record> records ) { if ( records != null ) { this.response.sections.get( ResponseSection.ADDITIONAL ).addAll( records ); } return this; } public Builder withAdditional( Record... records ) { if ( records != null ) { return withAdditional( Arrays.asList( records ) ); } else { return this; } } public Builder recursive( ) { this.response.recursive = true; return this; } public DnsResponse nxdomain( ) { this.response.nxdomain = true; return this.response; } public DnsResponse refused( ) { this.response.refused = true; return this.response; } public DnsResponse answer( List<? extends Record> records ) { if ( records != null ) { this.response.sections.get( ResponseSection.ANSWER ).addAll( records ); } return this.response; } public DnsResponse answer( Record... records ) { if ( records != null ) { return answer( Arrays.asList( records ) ); } else { return this.response; } } } private DnsResponse( Name name ) { this.name = name; } public static Builder forName( Name name ) { return new Builder( name ); } public boolean hasAnswer( ) { return !this.sections.isEmpty( ); } public Record[] section( ResponseSection s ) { if ( this.sections.containsKey( s ) ) { return this.sections.get( s ).toArray( new Record[] {} ); } else { return null; } } public boolean isAuthoritative( ) { return !this.recursive; } public boolean isNxdomain( ) { return this.nxdomain; } public boolean isRecursive( ) { return this.recursive; } public boolean isRefused( ) { return this.refused; } } public interface DnsRequest { Record getQuery(); InetAddress getRemoteAddress( ); InetAddress getLocalAddress( ); } public static abstract class DnsResolver implements Ordered { public abstract boolean checkAccepts( DnsRequest request ); public abstract DnsResponse lookupRecords( DnsRequest request ); protected static final int DEFAULT_ORDER = 0; @Override public int getOrder( ) { return DEFAULT_ORDER; } } /** * Returns the list of resolvers which accept the name from the given source address. */ private static Iterable<DnsResolver> resolversFor( final DnsRequest request ) { final List<DnsResolver> acceptingResolvers = Lists.newArrayList(Collections2.filter( resolvers.values( ), new Predicate<DnsResolver>( ) { @Override public boolean apply( final DnsResolver input ) { try { return input.checkAccepts( request ); } catch ( final Exception ex ) { return false; } } } )); Collections.sort(acceptingResolvers, new OrderComparator( ) ); return acceptingResolvers; } private static SetResponse lookupRecords( final Message response, final DnsRequest request ) { final Record query = request.getQuery( ); final InetAddress source = request.getRemoteAddress( ); final Name name = query.getName( ); final int type = query.getType( ); response.getHeader( ).setFlag( Flags.RA );// always mark the response w/ the recursion available // bit LOG.debug( "DnsResolver: " + RequestType.typeOf( type ) + " " + name ); for ( final DnsResolver r : DnsResolvers.resolversFor( request ) ) { try { final DnsResponse reply = r.lookupRecords( request ); if ( reply == null ) { LOG.debug( "DnsResolver: returned null " + name + " using " + r ); continue; } if ( reply.isAuthoritative( ) ) {// mark response.getHeader( ).setFlag( Flags.AA ); } if ( reply.isNxdomain( ) ) { try{ addRRset( name, response, new Record[] { DomainNameRecords.sourceOfAuthority( name ) }, type ); }catch(final Exception ex){ ; } response.getHeader( ).setRcode( Rcode.NXDOMAIN ); return SetResponse.ofType( SetResponse.NXDOMAIN ); } else if (reply.isRefused()) { response.getHeader().setRcode( Rcode.REFUSED ); return SetResponse.ofType( SetResponse.UNKNOWN ); } else if ( reply.hasAnswer( ) ) { for ( ResponseSection s : ResponseSection.values( ) ) { Record[] records = reply.section( s ); if ( records != null ) { addRRset( name, response, records, s.section( ) ); } } return SetResponse.ofType( SetResponse.SUCCESSFUL ); } else { return SetResponse.ofType( SetResponse.SUCCESSFUL ); } } catch ( final Exception ex ) { LOG.debug( "DnsResolver: failed for " + name + " using " + r + " because of: " + ex.getMessage( ), ex ); } } return SetResponse.ofType( SetResponse.UNKNOWN );// no dice, return unknown } @SuppressWarnings( "unchecked" ) @Override public boolean processClass( final Class candidate ) throws Exception { if ( DnsResolver.class.isAssignableFrom( candidate ) && !Modifier.isAbstract( candidate.getModifiers( ) ) ) { try { final DnsResolver resolver = ( DnsResolver ) candidate.newInstance( ); resolvers.putInstance( candidate, resolver ); return true; } catch ( final Exception ex ) { LOG.error( "Failed to create instance of DnsResolver: " + candidate + " because of: " + ex.getMessage( ) ); } } return false; } @Override public Double getPriority( ) { return 0.5d; } public static SetResponse findRecords( final Message response, final DnsRequest request ) { try { if ( !enabled || !Bootstrap.isOperational( ) ) { return SetResponse.ofType( SetResponse.UNKNOWN ); } else { final Iterable<DnsResolver> resolverList = DnsResolvers.resolversFor( request ); if ( Iterables.isEmpty( resolverList ) ) { return SetResponse.ofType( SetResponse.NXDOMAIN ); } else { return DnsResolvers.lookupRecords( response, request ); } } } catch ( final Exception ex ) { LOG.error( ex ); LOG.trace( ex, ex ); } return SetResponse.ofType( SetResponse.UNKNOWN ); } }