Repository: mageddo/dns-proxy-server
Pull request #414 (2023-04-12 23:06:54)

Introducing option to customize rcode
Relates to #386 


Patch:
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 11f9c349..33dc742d 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -12,9 +12,11 @@ for more details
 * Option to not comment out existing nameservers from resolv.conf [see the docs][1]
 * Option to disable remote solvers, see [the docs][2] for more details.
 * Fixing DPS auto connect feature stops working suddenly, see #408
+* Option to customize RCODE when no entries are found by all tried solvers [see the docs][3]
 
 [1]: http://mageddo.github.io/dns-proxy-server/3.15/en/3-configuration/
 [2]: https://mageddo.github.io/dns-proxy-server/3.15/en/3-configuration/#no-remote-servers
+[3]: https://mageddo.github.io/dns-proxy-server/3.15/en/3-configuration/#no-entries-response-code
 
 ## 3.14.5
 * Specify minimum required docker api version `1.21` (as DPS 2)
diff --git a/docs/content/3-configuration/_index.en.md b/docs/content/3-configuration/_index.en.md
index 6abe4ef3..f734a155 100755
--- a/docs/content/3-configuration/_index.en.md
+++ b/docs/content/3-configuration/_index.en.md
@@ -109,6 +109,13 @@ Active Env used to query local db entries. Default `` (Empty String).
 |-----|-------------|----------|
 |     | `activeEnv` |          |
 
+### No Entries Response Code
+Response code to use when no entries are returned by the configured solvers. Default: 3
+
+| Env                         | JSON                    | Terminal  |
+|-----------------------------|-------------------------|-----------|
+| MG_NO_ENTRIES_RESPONSE_CODE | `noEntriesResponseCode` | See -help |
+
 ### Local Entries Solving (LocalDB)
 See [Local Entries Solving][2] docs.
 
@@ -148,7 +155,8 @@ __Version 2__
   "serverProtocol": "UDP_TCP", 
   "dockerHost": null,
   "resolvConfOverrideNameServers": true,
-  "noRemoteServers": false
+  "noRemoteServers": false,
+  "noEntriesResponseCode": 3
 }
 ```
 
diff --git a/gradle.properties b/gradle.properties
index 88334ed6..26ab97ed 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,1 +1,1 @@
-version=3.15.12-snapshot
+version=3.15.13-snapshot
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/Config.java b/src/main/java/com/mageddo/dnsproxyserver/config/Config.java
index d91fd6ba..3ebf46c4 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/Config.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/Config.java
@@ -87,6 +87,8 @@ public class Config {
 
   private boolean noRemoteServers;
 
+  private int noEntriesResponseCode;
+
   public void resetConfigFile() {
     try {
       Files.deleteIfExists(this.getConfigPath());
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/Configs.java b/src/main/java/com/mageddo/dnsproxyserver/config/Configs.java
index d297a5ff..681d0ea0 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/Configs.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/Configs.java
@@ -71,6 +71,9 @@ public class Configs {
       .noRemoteServers(firstNonNullRequiring(
         env.getNoRemoteServers(), json.getNoRemoteServers(), flag.getNoRemoteServers()
       ))
+      .noEntriesResponseCode(firstNonNullRequiring(
+        env.getNoEntriesResponseCode(), json.getNoEntriesResponseCode(), flag.getNoEntriesResponseCode()
+      ))
       .build();
   }
 
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigEnv.java b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigEnv.java
index 6f7cb7de..11c5e307 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigEnv.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigEnv.java
@@ -43,6 +43,8 @@ public class ConfigEnv {
 
   private static final String MG_NO_REMOTE_SERVERS = "MG_NO_REMOTE_SERVERS";
 
+  private static final String MG_NO_ENTRIES_RESPONSE_CODE = "MG_NO_ENTRIES_RESPONSE_CODE";
+
   private Path currentPath;
   private String resolvConfPath;
   private String logFile;
@@ -55,6 +57,7 @@ public class ConfigEnv {
   private URI dockerHost;
   private Boolean resolvConfOverrideNameServers;
   private Boolean noRemoteServers;
+  private Integer noEntriesResponseCode;
 
   public static ConfigEnv fromEnv() {
     return ConfigEnv
@@ -71,6 +74,7 @@ public class ConfigEnv {
       .dockerHost(UriUtils.createURI(Envs.getStringOrNull(MG_DOCKER_HOST)))
       .resolvConfOverrideNameServers(Envs.getBooleanOrNull(MG_RESOLVCONF_OVERRIDE_NAMESERVERS))
       .noRemoteServers(Envs.getBooleanOrNull(MG_NO_REMOTE_SERVERS))
+      .noEntriesResponseCode(Envs.getIntegerOrNull(MG_NO_ENTRIES_RESPONSE_CODE))
       .build();
   }
 
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigFlag.java b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigFlag.java
index 0aa37fba..e052ce5a 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigFlag.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigFlag.java
@@ -156,6 +156,16 @@ public class ConfigFlag implements Callable<Boolean> {
   )
   private Boolean noRemoteServers;
 
+  @Option(
+    names = {"-no-entries-response-code", "--no-entries-response-code"},
+    description = """
+      Response code to use when no entries are returned by the configured solvers
+      (default 3) which means NXDOMAIN
+       """,
+    defaultValue = "3"
+  )
+  private Integer noEntriesResponseCode;
+
   @Option(
     names = {"-help", "--help"},
     description = "This message (default false)",
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJson.java b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJson.java
index a0910ede..d37f8b14 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJson.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJson.java
@@ -42,4 +42,6 @@ public interface ConfigJson {
   Boolean getResolvConfOverrideNameServers();
 
   Boolean getNoRemoteServers();
+
+  Integer getNoEntriesResponseCode();
 }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV1.java b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV1.java
index 36fb0e65..35b2595d 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV1.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV1.java
@@ -92,6 +92,11 @@ public class ConfigJsonV1 implements ConfigJson {
     return null;
   }
 
+  @Override
+  public Integer getNoEntriesResponseCode() {
+    return null;
+  }
+
   public ConfigJsonV2 toConfigV2() {
     return new ConfigJsonV2()
       .setDomain(this.getDomain())
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV2.java b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV2.java
index 8b7c1ed8..7a57f443 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV2.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV2.java
@@ -57,6 +57,8 @@ public class ConfigJsonV2 implements ConfigJson {
 
   private Boolean noRemoteServers;
 
+  private Integer noEntriesResponseCode;
+
   @JsonIgnore
   public List<IpAddr> getRemoteDnsServers() {
     return this.remoteDnsServers
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/RequestHandlerDefault.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/RequestHandlerDefault.java
index b0ce2fb2..a68a2803 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/RequestHandlerDefault.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/RequestHandlerDefault.java
@@ -1,5 +1,6 @@
 package com.mageddo.dnsproxyserver.server.dns;
 
+import com.mageddo.dnsproxyserver.config.Configs;
 import com.mageddo.dnsproxyserver.server.dns.solver.CacheName;
 import com.mageddo.dnsproxyserver.server.dns.solver.CacheName.Name;
 import com.mageddo.dnsproxyserver.server.dns.solver.Response;
@@ -24,6 +25,7 @@ public class RequestHandlerDefault implements RequestHandler {
 
   private final SolverProvider solverProvider;
   private final SolverCache cache;
+  private final int noEntriesRCode;
 
   @Inject
   public RequestHandlerDefault(
@@ -32,6 +34,7 @@ public class RequestHandlerDefault implements RequestHandler {
   ) {
     this.solverProvider = solverProvider;
     this.cache = cache;
+    this.noEntriesRCode = Configs.getInstance().getNoEntriesResponseCode();
   }
 
   @Override
@@ -102,7 +105,8 @@ public class RequestHandlerDefault implements RequestHandler {
     return null;
   }
 
-  public static Message buildDefaultRes(Message reqMsg) {
-    return Messages.nxDomain(reqMsg); // if all failed and returned null, then return as can't find
+  public Message buildDefaultRes(Message reqMsg) {
+    // if all failed and returned null, then return as can't find
+    return Messages.withResponseCode(reqMsg, this.noEntriesRCode);
   }
 }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/utils/Envs.java b/src/main/java/com/mageddo/dnsproxyserver/utils/Envs.java
index b3477251..8527a5f3 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/utils/Envs.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/utils/Envs.java
@@ -46,4 +46,15 @@ public class Envs {
   public static String getStringOrDefault(String env, String def) {
     return StringUtils.defaultIfBlank(System.getenv(env), def);
   }
+
+  public static Integer getIntegerOrNull(String env) {
+    return getIntegerOrDefault(env, null);
+  }
+
+  public static Integer getIntegerOrDefault(String env, Integer def) {
+    if (StringUtils.isBlank(System.getenv(env))) {
+      return def;
+    }
+    return Integer.parseInt(System.getenv(env));
+  }
 }


Test patch:
diff --git a/src/test/java/com/mageddo/dnsproxyserver/config/entrypoint/JsonConfigsTest.java b/src/test/java/com/mageddo/dnsproxyserver/config/entrypoint/JsonConfigsTest.java
index 43038403..e2847ce5 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/config/entrypoint/JsonConfigsTest.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/config/entrypoint/JsonConfigsTest.java
@@ -19,23 +19,23 @@ class JsonConfigsTest {
   @Test
   void mustParseVersion1ConvertAndSaveAsVersion2WhenChanged(@TempDir Path tempDir) throws Exception {
     // arrange
-    final var tempConfigPath = tempDir.resolve("config.tmp.json");
-    Files.copy(readAsStream("/config-json-v1-test/001.json"), tempConfigPath);
+    final var tempJsonConfigPath = tempDir.resolve("config.tmp.json");
+    Files.copy(readAsStream("/config-json-v1-test/001.json"), tempJsonConfigPath);
 
     // act
     // assert
 
-    final var configJson = JsonConfigs.loadConfig(tempConfigPath);
+    final var configJson = JsonConfigs.loadConfig(tempJsonConfigPath);
     assertTrue(configJson instanceof ConfigJsonV2, ClassUtils.getSimpleName(configJson));
-    assertEquals(JsonConfigs.VERSION_1, findVersion(tempConfigPath));
+    assertEquals(JsonConfigs.VERSION_1, findVersion(tempJsonConfigPath));
 
-    JsonConfigs.write(tempConfigPath, (ConfigJsonV2) configJson);
-    assertEquals(JsonConfigs.VERSION_2, findVersion(tempConfigPath));
+    JsonConfigs.write(tempJsonConfigPath, (ConfigJsonV2) configJson);
+    assertEquals(JsonConfigs.VERSION_2, findVersion(tempJsonConfigPath));
 
-    final var path = JsonConfigs.buildBackupPath(tempConfigPath);
+    final var path = JsonConfigs.buildBackupPath(tempJsonConfigPath);
     assertTrue(Files.exists(path), path.toString());
 
-    assertEquals(readAndSortJson("/json-configs-test/001.json"), readAndSortJson(tempConfigPath));
+    assertEquals(readAndSortJson("/json-configs-test/001.json"), readAndSortJson(tempJsonConfigPath));
   }
 
   @Test
diff --git a/src/test/resources/config-json-v1-test/002.json b/src/test/resources/config-json-v1-test/002.json
index 0faf2485..24c3876c 100644
--- a/src/test/resources/config-json-v1-test/002.json
+++ b/src/test/resources/config-json-v1-test/002.json
@@ -20,6 +20,7 @@
   "hostMachineHostname" : null,
   "logFile" : "console",
   "logLevel" : "DEBUG",
+  "noEntriesResponseCode": null,
   "noRemoteServers": null,
   "registerContainerNames" : false,
   "remoteDnsServers" : [ "8.8.8.8", "4.4.4.4" ],
diff --git a/src/test/resources/configs-test/001.json b/src/test/resources/configs-test/001.json
index d14a3bf6..422bbfc7 100644
--- a/src/test/resources/configs-test/001.json
+++ b/src/test/resources/configs-test/001.json
@@ -7,6 +7,7 @@
   "hostMachineHostname" : "host.docker",
   "logFile" : "console",
   "logLevel" : "INFO",
+  "noEntriesResponseCode" : 3,
   "noRemoteServers" : false,
   "registerContainerNames" : false,
   "remoteDnsServers" : [ {
diff --git a/src/test/resources/configs-test/002.json b/src/test/resources/configs-test/002.json
index 8d39a227..9b6160ca 100644
--- a/src/test/resources/configs-test/002.json
+++ b/src/test/resources/configs-test/002.json
@@ -20,6 +20,7 @@
   "hostMachineHostname" : null,
   "logFile" : null,
   "logLevel" : null,
+  "noEntriesResponseCode" : null,
   "noRemoteServers" : null,
   "registerContainerNames" : null,
   "remoteDnsServers" : [ ],
diff --git a/src/test/resources/configs-test/004.json b/src/test/resources/configs-test/004.json
index 5606c155..0074ada2 100644
--- a/src/test/resources/configs-test/004.json
+++ b/src/test/resources/configs-test/004.json
@@ -7,6 +7,7 @@
   "hostMachineHostname" : "batata.com",
   "logFile" : "/var/log/dns-proxy-server.log",
   "logLevel" : "DEBUG",
+  "noEntriesResponseCode": 3,
   "noRemoteServers" : false,
   "registerContainerNames" : true,
   "remoteDnsServers" : [ {
diff --git a/src/test/resources/flags-test/001.json b/src/test/resources/flags-test/001.json
index c24c479e..8b60ef3a 100644
--- a/src/test/resources/flags-test/001.json
+++ b/src/test/resources/flags-test/001.json
@@ -9,6 +9,7 @@
   "publishServicePort" : true,
   "logToFile" : "console",
   "logLevel" : "INFO",
+  "noEntriesResponseCode" : 3,
   "noRemoteServers" : false,
   "registerContainerNames" : false,
   "hostMachineHostname" : "host.docker",
diff --git a/src/test/resources/flags-test/002.txt b/src/test/resources/flags-test/002.txt
index e7c6ccea..db7d5dc7 100644
--- a/src/test/resources/flags-test/002.txt
+++ b/src/test/resources/flags-test/002.txt
@@ -2,7 +2,8 @@ Usage: <main class> [-default-dns] [-dps-network] [-dps-network-auto-connect] [-
                     [-register-container-names] [-resolvconf-override-name-servers] [-service-publish-web-port]
                     [-version] [-conf-path=<configPath>] [-docker-host=<dockerHost>] [-domain=<domain>]
                     [-host-machine-hostname=<hostMachineHostname>] [-log-file=<logToFile>] [-log-level=<logLevel>]
-                    [-server-port=<dnsServerPort>] [-service=<service>] [-web-server-port=<webServerPort>]
+                    [-no-entries-response-code=<noEntriesResponseCode>] [-server-port=<dnsServerPort>]
+                    [-service=<service>] [-web-server-port=<webServerPort>]
       -conf-path, --conf-path=<configPath>
                             The config file path (default conf/config.json)
       -default-dns, --default-dns
@@ -30,6 +31,10 @@ Usage: <main class> [-default-dns] [-dps-network] [-dps-network-auto-connect] [-
 
       -log-level, --log-level=<logLevel>
                             Log Level ERROR, WARNING, INFO, DEBUG (default INFO)
+      -no-entries-response-code, --no-entries-response-code=<noEntriesResponseCode>
+                            Response code to use when no entries are returned by the configured solvers
+                            (default 3) which means NXDOMAIN
+
       -no-remote-servers, --no-remote-servers
                             If remote servers like 8.8.8.8 must be disabled and only local solvers like docker
                             containers or local db must be used.
diff --git a/src/test/resources/json-configs-test/001.json b/src/test/resources/json-configs-test/001.json
index 162be974..960a23eb 100644
--- a/src/test/resources/json-configs-test/001.json
+++ b/src/test/resources/json-configs-test/001.json
@@ -24,6 +24,7 @@
   "hostMachineHostname": null,
   "logFile": "console",
   "logLevel": "DEBUG",
+  "noEntriesResponseCode" : null,
   "noRemoteServers" : null,
   "registerContainerNames": false,
   "remoteDnsServers": [