001/* 002 * Copyright 2015-2024 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.ArrayList; 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.dnsqueryresult.DnsQueryResult; 024import org.minidns.source.DnsDataSource; 025import org.minidns.util.MultipleIoException; 026 027/** 028 * A DNS client using a reliable strategy. First the configured resolver of the 029 * system are used, then, in case there is no answer, a fall back to iterative 030 * resolving is performed. 031 */ 032public class ReliableDnsClient extends AbstractDnsClient { 033 034 public enum Mode { 035 /** 036 * Try the recursive servers first and fallback to iterative resolving if it fails. This is the default mode. 037 */ 038 recursiveWithIterativeFallback, 039 040 /** 041 * Only try the recursive servers. This makes {@code ReliableDnsClient} behave like a {@link DnsClient}. 042 */ 043 recursiveOnly, 044 045 /** 046 * Only use iterative resolving. This makes {@code ReliableDnsClient} behave like a {@link IterativeDnsClient}. 047 */ 048 iterativeOnly, 049 } 050 051 private final IterativeDnsClient recursiveDnsClient; 052 private final DnsClient dnsClient; 053 054 private Mode mode = Mode.recursiveWithIterativeFallback; 055 056 public ReliableDnsClient(DnsCache dnsCache) { 057 super(dnsCache); 058 recursiveDnsClient = new IterativeDnsClient(dnsCache) { 059 @Override 060 protected DnsMessage.Builder newQuestion(DnsMessage.Builder questionMessage) { 061 questionMessage = super.newQuestion(questionMessage); 062 return ReliableDnsClient.this.newQuestion(questionMessage); 063 } 064 // TODO: Rename dnsMessage to result. 065 @Override 066 protected boolean isResponseCacheable(Question q, DnsQueryResult dnsMessage) { 067 boolean res = super.isResponseCacheable(q, dnsMessage); 068 return ReliableDnsClient.this.isResponseCacheable(q, dnsMessage) && res; 069 } 070 }; 071 dnsClient = new DnsClient(dnsCache) { 072 @Override 073 protected DnsMessage.Builder newQuestion(DnsMessage.Builder questionMessage) { 074 questionMessage = super.newQuestion(questionMessage); 075 return ReliableDnsClient.this.newQuestion(questionMessage); 076 } 077 // TODO: Rename dnsMessage to result. 078 @Override 079 protected boolean isResponseCacheable(Question q, DnsQueryResult dnsMessage) { 080 boolean res = super.isResponseCacheable(q, dnsMessage); 081 return ReliableDnsClient.this.isResponseCacheable(q, dnsMessage) && res; 082 } 083 }; 084 } 085 086 public ReliableDnsClient() { 087 this(DEFAULT_CACHE); 088 } 089 090 @Override 091 protected DnsQueryResult query(DnsMessage.Builder q) throws IOException { 092 DnsQueryResult dnsMessage = null; 093 String unacceptableReason = null; 094 List<IOException> ioExceptions = new ArrayList<>(); 095 096 if (mode != Mode.iterativeOnly) { 097 // Try a recursive query. 098 try { 099 dnsMessage = dnsClient.query(q); 100 if (dnsMessage != null) { 101 unacceptableReason = isResponseAcceptable(dnsMessage.response); 102 if (unacceptableReason == null) { 103 return dnsMessage; 104 } 105 } 106 } catch (IOException ioException) { 107 ioExceptions.add(ioException); 108 } 109 } 110 111 // Abort if we the are in "recursive only" mode. 112 if (mode == Mode.recursiveOnly) return dnsMessage; 113 114 // Eventually log that we fall back to iterative mode. 115 final Level FALLBACK_LOG_LEVEL = Level.FINE; 116 if (LOGGER.isLoggable(FALLBACK_LOG_LEVEL) && mode != Mode.iterativeOnly) { 117 String logString = "Resolution fall back to iterative mode because: "; 118 if (!ioExceptions.isEmpty()) { 119 logString += ioExceptions.get(0); 120 } else if (dnsMessage == null) { 121 logString += " DnsClient did not return a response"; 122 } else if (unacceptableReason != null) { 123 logString += unacceptableReason + ". Response:\n" + dnsMessage; 124 } else { 125 throw new AssertionError("This should never been reached"); 126 } 127 LOGGER.log(FALLBACK_LOG_LEVEL, logString); 128 } 129 130 try { 131 dnsMessage = recursiveDnsClient.query(q); 132 assert dnsMessage != null; 133 } catch (IOException ioException) { 134 ioExceptions.add(ioException); 135 } 136 137 if (dnsMessage == null) { 138 assert !ioExceptions.isEmpty(); 139 MultipleIoException.throwIfRequired(ioExceptions); 140 } 141 142 return dnsMessage; 143 } 144 145 @Override 146 protected DnsMessage.Builder newQuestion(DnsMessage.Builder questionMessage) { 147 return questionMessage; 148 } 149 150 @Override 151 protected boolean isResponseCacheable(Question q, DnsQueryResult result) { 152 return isResponseAcceptable(result.response) == null; 153 } 154 155 /** 156 * Check if the response from the system's nameserver is acceptable. Must return <code>null</code> if the response 157 * is acceptable, or a String describing why it is not acceptable. If the response is not acceptable then 158 * {@link ReliableDnsClient} will fall back to resolve the query iteratively. 159 * 160 * @param response the response we got from the system's nameserver. 161 * @return <code>null</code> if the response is acceptable, or a String if not. 162 */ 163 protected String isResponseAcceptable(DnsMessage response) { 164 return null; 165 } 166 167 @Override 168 public void setDataSource(DnsDataSource dataSource) { 169 super.setDataSource(dataSource); 170 recursiveDnsClient.setDataSource(dataSource); 171 dnsClient.setDataSource(dataSource); 172 } 173 174 /** 175 * Set the mode used when resolving queries. 176 * 177 * @param mode the mode to use. 178 */ 179 public void setMode(Mode mode) { 180 if (mode == null) { 181 throw new IllegalArgumentException("Mode must not be null."); 182 } 183 this.mode = mode; 184 } 185 186 public void setUseHardcodedDnsServers(boolean useHardcodedDnsServers) { 187 dnsClient.setUseHardcodedDnsServers(useHardcodedDnsServers); 188 } 189 190}