/************************************************************************* * 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.vm.dns; import static com.eucalyptus.util.dns.DnsResolvers.DnsRequest; import java.net.InetAddress; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.log4j.Logger; import org.xbill.DNS.Cache; import org.xbill.DNS.Credibility; import org.xbill.DNS.Lookup; import org.xbill.DNS.NSRecord; import org.xbill.DNS.Name; import org.xbill.DNS.RRset; import org.xbill.DNS.Record; import org.xbill.DNS.SetResponse; import org.xbill.DNS.Type; import com.eucalyptus.bootstrap.Bootstrap; import com.eucalyptus.configurable.ConfigurableClass; import com.eucalyptus.configurable.ConfigurableField; import com.eucalyptus.util.Subnets; import com.eucalyptus.util.dns.DnsResolvers.DnsResolver; import com.eucalyptus.util.dns.DnsResolvers.DnsResponse; import com.eucalyptus.util.dns.DnsResolvers.RequestType; import com.eucalyptus.util.dns.DomainNameRecords; import com.eucalyptus.util.dns.DomainNames; import com.eucalyptus.compute.common.internal.vm.VmInstance; import com.eucalyptus.vm.VmInstances; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.net.InetAddresses; /** * Implementation of a recursive resolver. The resolver works by taking whatever the QNAME and QTYPE * are and performing a lookup. There are two response cases: * * 1. The response type is the same as QTYPE: the returned response is passed back. * 2. The response type is CNAME: the target canonical name is queried starting over with * QNAME=CNAME and QTYPE=A. This happens repeatedly until the CNAME chain is fully resolved to the * root A record. The ordered set of CNAMEs followed by any A records are returned in the answer * section. * * All responses include corresponding NS records in the authority section and their A records in * the additional section. * * @author chris grzegorczyk <grze@eucalyptus.com> */ @ConfigurableClass( root = "dns.recursive", description = "Options controlling recursive DNS resolution and caching." ) public class RecursiveDnsResolver extends DnsResolver { private static Logger LOG = Logger.getLogger( RecursiveDnsResolver.class ); @ConfigurableField( description = "Enable the recursive DNS resolver. Note: dns.enable must also be 'true'", initial = "true" ) public static Boolean enabled = Boolean.TRUE; private static List<Name> subdomainsForName( Name name ) { final List<Name> names = Lists.newArrayList( name ); final String sub = parentDomainForName( name ); if ( sub.equals( "" ) || sub.equals( "." ) ) { names.add( Name.fromConstantString( "." ) ); } else { names.addAll( subdomainsForName( Name.fromConstantString( sub ) ) ); } return names; } private static String parentDomainForName( Name name ) { return name.toString( ).replaceAll( "\\A[^\\.]+\\.", "" ); } private static List<Record> lookupNSRecords( Name name, Cache cache ) { List<Name> subdomains = subdomainsForName( name ); for ( Name sub : subdomains ) { Lookup aLookup = new Lookup( sub, Type.NS ); aLookup.setCache( cache ); Record[] answers = aLookup.run( ); if ( answers != null && answers.length != 0 ) { return Arrays.asList( answers ); } } return Lists.newArrayList( ); } @Override public DnsResponse lookupRecords( final DnsRequest request ) { final Record query = request.getQuery( ); final Name name = query.getName( ); final int type = query.getType( ); final InetAddress source = request.getRemoteAddress( ); if (! enabled || !Subnets.isSystemManagedAddress( source )) return DnsResponse.forName( query.getName( ) ) .recursive( ) .refused(); final Cache cache = new NonExpiringCache( ); Lookup aLookup = new Lookup( name, type ); aLookup.setCache( cache ); Record[] found = aLookup.run( ); List<Record> queriedrrs = Arrays.asList( found != null ? found : new Record[] {} ); List<Name> cnames = ( List<Name> ) ( aLookup.getAliases( ).length > 0 ? Arrays.asList( aLookup.getAliases( ) ) : Lists.newArrayList( ) ); final Set<Record> answer = Sets.newLinkedHashSet( ); final Set<Record> authority = Sets.newLinkedHashSet( ); final Set<Record> additional = Sets.newLinkedHashSet( ); boolean iamAuthority = false; for ( Record aRec : queriedrrs ) { List<Record> nsRecs = lookupNSRecords( aRec.getName( ), cache ); for ( Record nsRec : nsRecs ) { if(nsRec.getName().equals(DomainNames.externalSubdomain())) iamAuthority = true; authority.add( nsRec ); Lookup nsLookup = new Lookup( ( ( NSRecord ) nsRec ).getTarget( ), type ); nsLookup.setCache( cache ); Record[] nsAnswers = nsLookup.run( ); if ( nsAnswers != null ) { additional.addAll( Arrays.asList( nsAnswers ) ); } } } for ( Name cnameRec : cnames ) { SetResponse sr = cache.lookupRecords( cnameRec, Type.CNAME, Credibility.ANY ); if ( sr != null && sr.isSuccessful( ) && sr.answers( ) != null ) { for ( RRset result : sr.answers( ) ) { Iterator rrs = result.rrs( false ); if ( rrs != null ) { for ( Object record : ImmutableSet.copyOf( rrs ) ) { answer.add( ( Record ) record ); } } } } } for ( Record record : ImmutableSet.copyOf( queriedrrs ) ) { if ( iamAuthority && DomainNames.isExternalSubdomain( record.getName() )){ final Name resolvedName = record.getName(); try { final Name instanceDomain = InstanceDomainNames.lookupInstanceDomain( resolvedName ); final InetAddress publicIp = InstanceDomainNames.toInetAddress( resolvedName.relativize( instanceDomain ) ); final VmInstance vm = VmInstances.lookupByPublicIp( publicIp.getHostAddress( ) ); final InetAddress instanceAddress = InetAddresses.forString( vm.getPrivateAddress( ) ); final Record privateARecord = DomainNameRecords.addressRecord( resolvedName, instanceAddress ); answer.add(privateARecord); } catch(final Exception ex) { answer.add( record ); continue; } } else { answer.add( record ); } } if((aLookup.getResult() == Lookup.SUCCESSFUL || aLookup.getResult() == Lookup.TYPE_NOT_FOUND ) && queriedrrs.size()==0){ List<Record> nsRecs = lookupNSRecords( name, cache ); for ( Record nsRec : nsRecs ) { authority.add( nsRec ); } } DnsResponse response = DnsResponse.forName( query.getName( ) ) .recursive( ) .withAuthority( Lists.newArrayList( authority ) ) .withAdditional( Lists.newArrayList( additional ) ) .answer( Lists.newArrayList( answer ) ); if(aLookup.getResult() == Lookup.HOST_NOT_FOUND && queriedrrs.size()==0){ response = DnsResponse.forName( query.getName( ) ) .recursive( ) .withAuthority( Lists.newArrayList( authority ) ) .nxdomain(); } return response; } /** * This resolver works when it is: * 1. Enabled * 2. The query is absolute * 3. The name/address is not in a Eucalyptus controlled subdomain * * @see com.eucalyptus.util.dns.DnsResolvers.DnsResolver#checkAccepts(DnsRequest) */ @Override public boolean checkAccepts( final DnsRequest request ) { final Record query = request.getQuery( ); final InetAddress source = request.getRemoteAddress( ); if ( !Bootstrap.isOperational( ) ) { return false; } else if ( ( RequestType.A.apply( query ) || RequestType.AAAA.apply( query ) || RequestType.MX.apply(query)) && query.getName( ).isAbsolute( ) && !DomainNames.isSystemSubdomain( query.getName( ) ) ) { return true; } else if ( RequestType.PTR.apply( query ) && !Subnets.isSystemManagedAddress( DomainNameRecords.inAddrArpaToInetAddress( query.getName( ) ) ) ) { return true; } return false; } @Override public int getOrder( ) { return DEFAULT_ORDER + 1; } @Override public String toString( ) { return this.getClass( ).getSimpleName( ); } }