001/* 002 * Copyright 2015-2018 the original author or authors 003 * 004 * This software is licensed under the Apache License, Version 2.0, 005 * the GNU Lesser General Public License version 2 or later ("LGPL") 006 * and the WTFPL. 007 * You may choose either license to govern your use of this software only 008 * upon the condition that you accept all of the terms of either 009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. 010 */ 011package org.minidns.iterative; 012 013import java.io.IOException; 014import java.util.LinkedList; 015import java.util.List; 016import java.util.logging.Level; 017 018import org.minidns.AbstractDnsClient; 019import org.minidns.DnsCache; 020import org.minidns.DnsClient; 021import org.minidns.dnsmessage.DnsMessage; 022import org.minidns.dnsmessage.Question; 023import org.minidns.source.DnsDataSource; 024import org.minidns.util.MultipleIoException; 025 026/** 027 * A DNS client using a reliable strategy. First the configured resolver of the 028 * system are used, then, in case there is no answer, a fall back to iterative 029 * resolving is performed. 030 */ 031public class ReliableDnsClient extends AbstractDnsClient { 032 033 public enum Mode { 034 /** 035 * Try the recursive servers first and fallback to iterative resolving if it fails. This is the default mode. 036 */ 037 recursiveWithIterativeFallback, 038 039 /** 040 * Only try the recursive servers. This makes {@code ReliableDnsClient} behave like a {@link DnsClient}. 041 */ 042 recursiveOnly, 043 044 /** 045 * Only use iterative resolving. This makes {@code ReliableDnsClient} behave like a {@link IterativeDnsClient}. 046 */ 047 iterativeOnly, 048 } 049 050 private final IterativeDnsClient recursiveDnsClient; 051 private final DnsClient dnsClient; 052 053 private Mode mode = Mode.recursiveWithIterativeFallback; 054 055 public ReliableDnsClient(DnsCache dnsCache) { 056 super(dnsCache); 057 recursiveDnsClient = new IterativeDnsClient(dnsCache) { 058 @Override 059 protected DnsMessage.Builder newQuestion(DnsMessage.Builder questionMessage) { 060 questionMessage = super.newQuestion(questionMessage); 061 return ReliableDnsClient.this.newQuestion(questionMessage); 062 } 063 @Override 064 protected boolean isResponseCacheable(Question q, DnsMessage dnsMessage) { 065 boolean res = super.isResponseCacheable(q, dnsMessage); 066 return ReliableDnsClient.this.isResponseCacheable(q, dnsMessage) && res; 067 } 068 }; 069 dnsClient = new DnsClient(dnsCache) { 070 @Override 071 protected DnsMessage.Builder newQuestion(DnsMessage.Builder questionMessage) { 072 questionMessage = super.newQuestion(questionMessage); 073 return ReliableDnsClient.this.newQuestion(questionMessage); 074 } 075 @Override 076 protected boolean isResponseCacheable(Question q, DnsMessage dnsMessage) { 077 boolean res = super.isResponseCacheable(q, dnsMessage); 078 return ReliableDnsClient.this.isResponseCacheable(q, dnsMessage) && res; 079 } 080 }; 081 } 082 083 public ReliableDnsClient() { 084 this(DEFAULT_CACHE); 085 } 086 087 @Override 088 protected DnsMessage query(DnsMessage.Builder q) throws IOException { 089 DnsMessage dnsMessage = null; 090 String unacceptableReason = null; 091 List<IOException> ioExceptions = new LinkedList<>(); 092 093 if (mode != Mode.iterativeOnly) { 094 // Try a recursive query. 095 try { 096 dnsMessage = dnsClient.query(q); 097 if (dnsMessage != null) { 098 unacceptableReason = isResponseAcceptable(dnsMessage); 099 if (unacceptableReason == null) { 100 return dnsMessage; 101 } 102 } 103 } catch (IOException ioException) { 104 ioExceptions.add(ioException); 105 } 106 } 107 108 // Abort if we the are in "recursive only" mode. 109 if (mode == Mode.recursiveOnly) return dnsMessage; 110 111 // Eventually log that we fall back to iterative mode. 112 final Level FALLBACK_LOG_LEVEL = Level.FINE; 113 if (LOGGER.isLoggable(FALLBACK_LOG_LEVEL) && mode != Mode.iterativeOnly) { 114 String logString = "Resolution fall back to iterative mode because: "; 115 if (!ioExceptions.isEmpty()) { 116 logString += ioExceptions.get(0); 117 } else if (dnsMessage == null) { 118 logString += " DnsClient did not return a response"; 119 } else if (unacceptableReason != null) { 120 logString += unacceptableReason + ". Response:\n" + dnsMessage; 121 } else { 122 throw new AssertionError("This should never been reached"); 123 } 124 LOGGER.log(FALLBACK_LOG_LEVEL, logString); 125 } 126 127 try { 128 dnsMessage = recursiveDnsClient.query(q); 129 } catch (IOException ioException) { 130 ioExceptions.add(ioException); 131 } 132 133 if (dnsMessage == null) { 134 MultipleIoException.throwIfRequired(ioExceptions); 135 } 136 137 return dnsMessage; 138 } 139 140 @Override 141 protected DnsMessage.Builder newQuestion(DnsMessage.Builder questionMessage) { 142 return questionMessage; 143 } 144 145 @Override 146 protected boolean isResponseCacheable(Question q, DnsMessage dnsMessage) { 147 return isResponseAcceptable(dnsMessage) == null; 148 } 149 150 /** 151 * Check if the response from the system's nameserver is acceptable. Must return <code>null</code> if the response 152 * is acceptable, or a String describing why it is not acceptable. If the response is not acceptable then 153 * {@link ReliableDnsClient} will fall back to resolve the query iteratively. 154 * 155 * @param response the response we got from the system's nameserver. 156 * @return <code>null</code> if the response is acceptable, or a String if not. 157 */ 158 protected String isResponseAcceptable(DnsMessage response) { 159 return null; 160 } 161 162 @Override 163 public void setDataSource(DnsDataSource dataSource) { 164 super.setDataSource(dataSource); 165 recursiveDnsClient.setDataSource(dataSource); 166 dnsClient.setDataSource(dataSource); 167 } 168 169 /** 170 * Set the mode used when resolving queries. 171 * 172 * @param mode the mode to use. 173 */ 174 public void setMode(Mode mode) { 175 if (mode == null) { 176 throw new IllegalArgumentException("Mode must not be null."); 177 } 178 this.mode = mode; 179 } 180}