Repository: mageddo/dns-proxy-server
Pull request #393 (2023-03-22 01:12:35)

Feature: Recursion Compliance
Relates to #392 


Patch:
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 23e2d87b..ecd6eede 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -1,5 +1,6 @@
 ## 3.15
 * Support for AAAA records on docker container solving.
+* Formalize Recursion Available on query responses as DPS supports this feature already, see #392
 
 ## 3.14.5
 * Specify minimum required docker api version `1.21` (as DPS 2)
diff --git a/gradle.properties b/gradle.properties
index eb6d4027..266413bd 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,1 +1,1 @@
-version=3.15.0-snapshot
+version=3.15.1-snapshot
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/Messages.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/Messages.java
index 8de8f951..d8c6eed8 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/Messages.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/Messages.java
@@ -1,6 +1,6 @@
 package com.mageddo.dnsproxyserver.server.dns;
 
-import com.mageddo.dnsproxyserver.config.Config;
+import com.mageddo.dnsproxyserver.config.Config.Entry;
 import com.mageddo.dnsproxyserver.server.dns.solver.Response;
 import com.mageddo.dnsproxyserver.utils.Ips;
 import lombok.SneakyThrows;
@@ -21,6 +21,9 @@ import java.util.Optional;
 
 public class Messages {
 
+  private Messages() {
+  }
+
   public static final long DEFAULT_TTL = 30L;
   public static final Duration DEFAULT_TTL_DURATION = Duration.ofSeconds(DEFAULT_TTL);
 
@@ -105,26 +108,6 @@ public class Messages {
     return section.get(0);
   }
 
-  public static Message nxDomain(Message query) {
-    final var res = query.clone();
-    final var header = res.getHeader();
-    header.setRcode(Rcode.NXDOMAIN);
-    header.setFlag(Flags.QR);
-    return res;
-  }
-
-  @SneakyThrows
-  public static Message cnameResponse(Message query, Integer ttl, String hostname) {
-    final var res = withNoErrorResponse(query.clone());
-    final var answer = new CNAMERecord(
-      res.getQuestion().getName(),
-      DClass.IN, ttl,
-      Name.fromString(Hostnames.toAbsoluteName(hostname))
-    );
-    res.addRecord(answer, Section.ANSWER);
-    return res;
-  }
-
   public static Message aQuestion(String host) {
     return Message.newQuery(query(host, Type.A));
   }
@@ -146,8 +129,8 @@ public class Messages {
       ;
   }
 
-  public static Config.Entry.Type findQuestionType(Message msg) {
-    return Config.Entry.Type.of(findQuestionTypeCode(msg));
+  public static Entry.Type findQuestionType(Message msg) {
+    return Entry.Type.of(findQuestionTypeCode(msg));
   }
 
   /**
@@ -195,17 +178,19 @@ public class Messages {
     return res;
   }
 
-  static Message clone(Message msg) {
-    if (msg == null) {
-      return null;
-    }
-    return msg.clone();
+  public static Message nxDomain(Message query) {
+    return withResponseCode(query.clone(), Rcode.NXDOMAIN);
   }
 
-  static Message withNoErrorResponse(Message res) {
-    final var header = res.getHeader();
-    header.setFlag(Flags.QR);
-    header.setRcode(Rcode.NOERROR);
+  @SneakyThrows
+  public static Message cnameResponse(Message query, Integer ttl, String hostname) {
+    final var res = withNoErrorResponse(query.clone());
+    final var answer = new CNAMERecord(
+      res.getQuestion().getName(),
+      DClass.IN, ttl,
+      Name.fromString(Hostnames.toAbsoluteName(hostname))
+    );
+    res.addRecord(answer, Section.ANSWER);
     return res;
   }
 
@@ -233,4 +218,37 @@ public class Messages {
     }
     return Messages.aAnswer(query, ip);
   }
+
+  static Message withNoErrorResponse(Message res) {
+    return withResponseCode(res, Rcode.NOERROR);
+  }
+
+  static Message withResponseCode(Message res, int rRode) {
+    withDefaultResponseHeaders(res);
+    res.getHeader().setRcode(rRode);
+    return res;
+  }
+
+  static Message withDefaultResponseHeaders(Message res) {
+    final var header = res.getHeader();
+    header.setFlag(Flags.QR);
+    header.setFlag(Flags.RA);
+    return res;
+  }
+
+  static Message clone(Message msg) {
+    if (msg == null) {
+      return null;
+    }
+    return msg.clone();
+  }
+
+  public static Message setFlag(Message m, int flag) {
+    m.getHeader().setFlag(flag);
+    return m;
+  }
+
+  public static boolean hasFlag(Message msg, int flag) {
+    return msg.getHeader().getFlag(flag);
+  }
 }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/Responses.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/Responses.java
new file mode 100644
index 00000000..80ed2994
--- /dev/null
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/Responses.java
@@ -0,0 +1,10 @@
+package com.mageddo.dnsproxyserver.server.dns.solver;
+
+public class Responses {
+  public static boolean hasFlag(Response r, int flag) {
+    if (r == null) {
+      return false;
+    }
+    return r.getMessage().getHeader().getFlag(flag);
+  }
+}
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverDocker.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverDocker.java
index d8efbe53..4b30095f 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverDocker.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverDocker.java
@@ -39,7 +39,10 @@ public class SolverDocker implements Solver {
     final var version = toVersion(Type.of(type));
     return HostnameMatcher.match(askedHost, version, hostname -> {
       final var ip = this.containerSolvingService.findBestHostIP(hostname);
-      return Objects.mapOrNull(ip, (it) -> Response.of(Messages.answer(query, ip, hostname.getVersion())));
+      return Objects.mapOrNull(
+        ip,
+        (it) -> Response.of(Messages.answer(query, ip, hostname.getVersion()))
+      );
     });
 
   }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverRemote.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverRemote.java
index e4f819f0..350f9a8d 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverRemote.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverRemote.java
@@ -1,8 +1,10 @@
 package com.mageddo.dnsproxyserver.server.dns.solver;
 
+import com.mageddo.dnsproxyserver.server.dns.Messages;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.ClassUtils;
+import org.xbill.DNS.Flags;
 import org.xbill.DNS.Message;
 import org.xbill.DNS.Rcode;
 import org.xbill.DNS.Resolver;
@@ -30,7 +32,9 @@ public class SolverRemote implements Solver {
     for (int i = 0; i < this.delegate.resolvers().size(); i++) {
       final Resolver resolver = this.delegate.resolvers().get(i);
       try {
-        final var res = resolver.send(query);
+
+        final var res = Messages.setFlag(resolver.send(query), Flags.RA);
+
         if (res.getRcode() == Rcode.NOERROR) {
           log.trace("status=found, i={}, req={}, res={}, server={}", i, simplePrint(query), simplePrint(res), resolver);
           return Response.of(res, DEFAULT_SUCCESS_TTL);


Test patch:
diff --git a/src/test/java/com/mageddo/dnsproxyserver/server/dns/MessagesTest.java b/src/test/java/com/mageddo/dnsproxyserver/server/dns/MessagesTest.java
index e0447a84..89301a94 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/server/dns/MessagesTest.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/server/dns/MessagesTest.java
@@ -23,8 +23,49 @@ class MessagesTest {
     // assert
     assertFalse(query.getHeader().getFlag(Flags.QR));
     assertTrue(res.getHeader().getFlag(Flags.QR));
+    assertTrue(res.getHeader().getFlag(Flags.RA));
     assertEquals(Rcode.NXDOMAIN, res.getHeader().getRcode());
 
   }
 
+  @Test
+  void mustRespondRaFlagEvenWhenRdIsNotSetForARecord(){
+
+    // arrange
+    final var query = MessageTemplates.acmeAQuery();
+
+    // act
+    final var res = Messages.aAnswer(query, "0.0.0.0");
+
+    // assert
+    assertFalse(query.getHeader().getFlag(Flags.QR));
+    assertTrue(query.getHeader().getFlag(Flags.RD));
+
+    final var resHeader = res.getHeader();
+    assertTrue(resHeader.getFlag(Flags.QR));
+    assertTrue(resHeader.getFlag(Flags.RA));
+    assertEquals(Rcode.NOERROR, resHeader.getRcode());
+
+  }
+
+  @Test
+  void mustRespondRaFlagEvenWhenRdIsNotSetForAAAARecord(){
+
+    // arrange
+    final var query = MessageTemplates.acmeAQuery();
+
+    // act
+    final var res = Messages.quadAnswer(query, "0.0.0.0");
+
+    // assert
+    assertFalse(query.getHeader().getFlag(Flags.QR));
+    assertTrue(query.getHeader().getFlag(Flags.RD));
+
+    final var resHeader = res.getHeader();
+    assertTrue(resHeader.getFlag(Flags.QR));
+    assertTrue(resHeader.getFlag(Flags.RA));
+    assertEquals(Rcode.NOERROR, resHeader.getRcode());
+
+  }
+
 }
diff --git a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverDockerTest.java b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverDockerTest.java
index 53e4c8d5..9e883034 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverDockerTest.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverDockerTest.java
@@ -13,6 +13,7 @@ import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.xbill.DNS.Flags;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -88,7 +89,7 @@ class SolverDockerTest {
 
     // assert
     assertNotNull(res);
-
+    assertTrue(Responses.hasFlag(res, Flags.RA));
     final var resText = res.toString();
     assertTrue(resText.contains(ip), resText);
     verify(this.containerSolvingService).findBestHostIP(this.hostnameQueryCaptor.capture());
diff --git a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverRemoteTest.java b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverRemoteTest.java
index 6ef2377d..7de91a8a 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverRemoteTest.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverRemoteTest.java
@@ -7,6 +7,7 @@ import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Spy;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.xbill.DNS.Flags;
 import org.xbill.DNS.Resolver;
 
 import java.io.IOException;
@@ -14,6 +15,7 @@ import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
@@ -99,4 +101,30 @@ class SolverRemoteTest {
     assertNull(res);
   }
 
+
+  @Test
+  void mustReturnRaEvenWhenRemoteServerDoesntReturns() throws Exception {
+    // arrange
+    final var query = MessageTemplates.acmeAQuery();
+    final var res = MessageTemplates.buildAAnswer(query);
+    res.getHeader().unsetFlag(Flags.RA);
+
+    doReturn(res)
+      .when(this.resolver)
+      .send(any())
+    ;
+
+    doReturn(List.of(this.resolver))
+      .when(this.resolvers)
+      .resolvers()
+    ;
+
+    // act
+    final var result = this.solverRemote.handle(query);
+
+    // assert
+    assertTrue(Responses.hasFlag(result, Flags.RA));
+    assertEquals(SolverRemote.DEFAULT_SUCCESS_TTL, result.getTtl());
+  }
+
 }
diff --git a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverSystemCompTest.java b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverSystemCompTest.java
index aaf88367..9cd12882 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverSystemCompTest.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverSystemCompTest.java
@@ -7,6 +7,7 @@ import dagger.sheath.InjectMock;
 import dagger.sheath.junit.DaggerTest;
 import org.hamcrest.CoreMatchers;
 import org.junit.jupiter.api.Test;
+import org.xbill.DNS.Flags;
 import testing.ContextSupplier;
 import testing.Events;
 
@@ -14,6 +15,7 @@ import javax.inject.Inject;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
@@ -41,8 +43,10 @@ class SolverSystemCompTest {
     final var res = this.solver.handle(query);
 
     // assert
-    final var answer = Messages.findFirstAnswerRecordStr(res.getMessage());
+    final var msg = res.getMessage();
+    final var answer = Messages.findFirstAnswerRecordStr(msg);
     assertThat(answer, CoreMatchers.containsString(hostname));
+    assertTrue(Messages.hasFlag(msg, Flags.RA));
     assertEquals("host.docker.\t\t30\tIN\tA\t192.168.0.1", answer);
 
     verify(this.machineService).findHostMachineIP();