@@ -55,6 +55,9 @@ public class DnsProxyServer {
5555 private ServerSocket tcpServerSocket ;
5656 private ExecutorService executor ;
5757 private final AtomicInteger dohFailures = new AtomicInteger (0 );
58+ private static final int CIRCUIT_BREAKER_THRESHOLD = 10 ;
59+ private static final long CIRCUIT_BREAKER_COOLDOWN_MS = 60_000 ;
60+ private volatile long circuitOpenUntil = 0 ;
5861
5962 private DnsProxyServer (Context context ) {
6063 this .context = context .getApplicationContext ();
@@ -221,32 +224,38 @@ private void runServer() {
221224 */
222225 private void handleQuery (byte [] queryData , InetAddress clientAddress , int clientPort ) {
223226 try {
224- // Get the DoH client
225227 SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences (context );
226- String endpoint = prefs .getString ("doh_endpoint" , BuildConfig .DEFAULT_DOH_ENDPOINT );
227- DnsOverHttpsClient dohClient = DnsOverHttpsClient .getInstance (endpoint );
228-
229- // Forward the query via DoH (the query is already in DNS wire format)
230- byte [] responseData = dohClient .resolve (queryData );
228+ byte [] responseData = null ;
229+
230+ // Circuit breaker: skip DoH if we recently had too many failures
231+ boolean circuitOpen = System .currentTimeMillis () < circuitOpenUntil ;
232+ if (!circuitOpen ) {
233+ String endpoint = prefs .getString ("doh_endpoint" , BuildConfig .DEFAULT_DOH_ENDPOINT );
234+ DnsOverHttpsClient dohClient = DnsOverHttpsClient .getInstance (endpoint );
235+ responseData = dohClient .resolve (queryData );
236+ }
231237
232238 if (responseData != null ) {
233239 dohFailures .set (0 );
234- // Send response back to client
240+ circuitOpenUntil = 0 ;
235241 DatagramSocket socket = serverSocket ;
236242 if (socket == null || socket .isClosed ()) return ;
237243 DatagramPacket response = new DatagramPacket (
238244 responseData , responseData .length , clientAddress , clientPort );
239245 socket .send (response );
240246 Log .d (TAG , "DoH query successful, response sent to " + clientAddress + ":" + clientPort );
241247 } else {
242- int failures = dohFailures .incrementAndGet ();
243- Log .w (TAG , "DoH query returned null response, failures=" + failures );
244-
245- if (failures >= 10 ) {
246- if (eu .faircode .netguard .Util .isInternetWorking (context )) {
247- Log .w (TAG , "DoH server unreachable even though internet is working, showing notification" );
248- eu .faircode .netguard .ServiceSinkhole .dohError (context );
249- dohFailures .set (0 ); // Reset to wait for the next 10 failures
248+ if (!circuitOpen ) {
249+ int failures = dohFailures .incrementAndGet ();
250+ Log .w (TAG , "DoH query returned null response, failures=" + failures );
251+
252+ if (failures >= CIRCUIT_BREAKER_THRESHOLD ) {
253+ circuitOpenUntil = System .currentTimeMillis () + CIRCUIT_BREAKER_COOLDOWN_MS ;
254+ dohFailures .set (0 );
255+ if (eu .faircode .netguard .Util .isInternetWorking (context )) {
256+ Log .w (TAG , "DoH circuit breaker tripped, skipping DoH for 60s" );
257+ eu .faircode .netguard .ServiceSinkhole .dohError (context );
258+ }
250259 }
251260 }
252261
@@ -335,26 +344,35 @@ private void handleTcpConnection(Socket client) {
335344 // Let's copy the resolution logic here for safety.
336345
337346 SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences (context );
338- String endpoint = prefs .getString ("doh_endpoint" , BuildConfig .DEFAULT_DOH_ENDPOINT );
339- DnsOverHttpsClient dohClient = DnsOverHttpsClient .getInstance (endpoint );
347+ byte [] responseData = null ;
340348
341- byte [] responseData = dohClient .resolve (queryData );
349+ boolean circuitOpen = System .currentTimeMillis () < circuitOpenUntil ;
350+ if (!circuitOpen ) {
351+ String endpoint = prefs .getString ("doh_endpoint" , BuildConfig .DEFAULT_DOH_ENDPOINT );
352+ DnsOverHttpsClient dohClient = DnsOverHttpsClient .getInstance (endpoint );
353+ responseData = dohClient .resolve (queryData );
354+ }
342355
343356 if (responseData != null ) {
344357 dohFailures .set (0 );
345- // Write response with 2-byte length prefix
358+ circuitOpenUntil = 0 ;
346359 out .writeShort (responseData .length );
347360 out .write (responseData );
348361 out .flush ();
349362 Log .d (TAG , "DoH TCP query successful" );
350363 } else {
351- int failures = dohFailures .incrementAndGet ();
352- if (failures >= 10 && eu .faircode .netguard .Util .isInternetWorking (context )) {
353- eu .faircode .netguard .ServiceSinkhole .dohError (context );
354- dohFailures .set (0 );
364+ if (!circuitOpen ) {
365+ int failures = dohFailures .incrementAndGet ();
366+ if (failures >= CIRCUIT_BREAKER_THRESHOLD ) {
367+ circuitOpenUntil = System .currentTimeMillis () + CIRCUIT_BREAKER_COOLDOWN_MS ;
368+ dohFailures .set (0 );
369+ if (eu .faircode .netguard .Util .isInternetWorking (context )) {
370+ Log .w (TAG , "DoH circuit breaker tripped (TCP), skipping DoH for 60s" );
371+ eu .faircode .netguard .ServiceSinkhole .dohError (context );
372+ }
373+ }
355374 }
356375
357- // Fallback to standard DNS if enabled
358376 boolean dnsFallback = prefs .getBoolean ("doh_dns_fallback" , false );
359377 if (dnsFallback ) {
360378 byte [] fallbackResponse = resolveViaStandardDns (queryData );
0 commit comments