Repository: mageddo/dns-proxy-server
Pull request #402 (2023-03-31 02:27:26)

LocalDB: Support for Type AAAA Entries (IPv6) 
Relates to #389 


Patch:
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 9191019c..7dba3008 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -1,5 +1,5 @@
 ## 3.15
-* Support for AAAA records on docker container and system solving.
+* Support for AAAA records on Docker container, LocalDB and System solving.
 * Formalize Recursion Available on query responses as DPS supports this feature already, see #392
 * Defined a new process of releasing the stable versions, see "Getting Started -> Release Version Control" 
 for more details
diff --git a/gradle.properties b/gradle.properties
index 6ab1faef..cadebd92 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,1 +1,1 @@
-version=3.15.5-snapshot
+version=3.15.6-snapshot
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/Config.java b/src/main/java/com/mageddo/dnsproxyserver/config/Config.java
index a60315b9..e8729752 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/Config.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/Config.java
@@ -1,9 +1,9 @@
 package com.mageddo.dnsproxyserver.config;
 
 import com.mageddo.dnsproxyserver.config.entrypoint.LogLevel;
-import com.mageddo.net.IpAddr;
 import com.mageddo.dnsproxyserver.server.dns.SimpleServer;
 import com.mageddo.net.IP;
+import com.mageddo.net.IpAddr;
 import lombok.Builder;
 import lombok.NonNull;
 import lombok.RequiredArgsConstructor;
@@ -118,15 +118,22 @@ public class Config {
   @Value
   @Builder(builderClassName = "EntryBuilder", buildMethodName = "_build", toBuilder = true)
   public static class Entry {
+
     @NonNull
     private Long id;
 
     @NonNull
     private String hostname;
 
-    private String ip; // hostname ip when type=A
+    /**
+     * Used when {@link #type} in {@link Type#AAAA} , {@link Type#A}
+     */
+    private String ip;
 
-    private String target; // target hostname when type=CNAME
+    /**
+     * Target hostname when {@link #type} = {@link Type#CNAME}
+     */
+    private String target;
 
     @NonNull
     private Integer ttl;
@@ -156,7 +163,7 @@ public class Config {
        */
       private final int type;
 
-      public boolean isNot(Type ... types) {
+      public boolean isNot(Type... types) {
         return isNot(this.type, types);
       }
 
@@ -200,6 +207,9 @@ public class Config {
         };
       }
 
+      public boolean isAddressSolving() {
+        return is(this, Config.Entry.Type.A, Config.Entry.Type.AAAA);
+      }
     }
   }
 }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/ConfigDAOJson.java b/src/main/java/com/mageddo/dnsproxyserver/config/ConfigDAOJson.java
index e134f02c..83b518f5 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/ConfigDAOJson.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/ConfigDAOJson.java
@@ -39,12 +39,20 @@ public class ConfigDAOJson implements ConfigDAO {
   }
 
   @Override
-  public Config.Entry findEntryForActiveEnv(HostnameQuery hostname) {
+  public Config.Entry findEntryForActiveEnv(HostnameQuery query) {
     final var env = this.findActiveEnv();
     return env.getEntries()
       .stream()
-      .filter(it -> hostname.matches(it.getHostname()))
-      .findFirst()
+      .filter(it -> query.matches(it.getHostname()))
+      .min((o1, o2) -> {
+        if (o1.getType() == o2.getType()) {
+          return 0;
+        }
+        if (query.isTypeEqualTo(o1.getType())) {
+          return -1;
+        }
+        return 1;
+      })
       .orElse(null);
   }
 
@@ -198,5 +206,6 @@ public class ConfigDAOJson implements ConfigDAO {
   interface TriConsumer<A, B, C> {
     void accept(A a, B b, C c);
   }
+
 }
 
diff --git a/src/main/java/com/mageddo/dnsproxyserver/docker/ContainerSolvingService.java b/src/main/java/com/mageddo/dnsproxyserver/docker/ContainerSolvingService.java
index ee4e0ce4..789acd87 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/docker/ContainerSolvingService.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/docker/ContainerSolvingService.java
@@ -41,7 +41,7 @@ public class ContainerSolvingService {
   private final DockerDAO dockerDAO;
   private final DockerNetworkDAO networkDAO;
 
-  public String findBestHostIP(HostnameQuery host) {
+  public Entry findBestMatch(HostnameQuery host) {
     final var stopWatch = StopWatch.createStarted();
     final var matchedContainers = this.findMatchingContainers(host);
     final var foundIp = matchedContainers
@@ -49,8 +49,16 @@ public class ContainerSolvingService {
       .map(it -> this.findBestIpMatch(it, host.getVersion()))
       .findFirst()
       .orElse(null);
-    log.trace("status=findDone, host={}, found={}, time={}", host, foundIp, stopWatch.getTime());
-    return foundIp;
+    final var hostnameMatched = !matchedContainers.isEmpty();
+    log.trace(
+      "status=findDone, host={}, found={}, hostnameMatched={}, time={}",
+      host, foundIp, hostnameMatched, stopWatch.getTime()
+    );
+    return Entry
+      .builder()
+      .hostnameMatched(hostnameMatched)
+      .ip(IP.of(foundIp))
+      .build();
   }
 
   public String findBestIpMatch(InspectContainerResponse inspect) {
diff --git a/src/main/java/com/mageddo/dnsproxyserver/docker/Entry.java b/src/main/java/com/mageddo/dnsproxyserver/docker/Entry.java
new file mode 100644
index 00000000..9c65b24f
--- /dev/null
+++ b/src/main/java/com/mageddo/dnsproxyserver/docker/Entry.java
@@ -0,0 +1,17 @@
+package com.mageddo.dnsproxyserver.docker;
+
+import com.mageddo.net.IP;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+public class Entry {
+
+  private boolean hostnameMatched;
+  private IP ip;
+
+  public String getIpText() {
+    return this.ip != null ? this.ip.toText() : null;
+  }
+}
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 4a1a6a66..f6405eed 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/Messages.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/Messages.java
@@ -8,6 +8,7 @@ import com.mageddo.dnsproxyserver.utils.Ips;
 import com.mageddo.net.IP;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.xbill.DNS.AAAARecord;
 import org.xbill.DNS.ARecord;
 import org.xbill.DNS.CNAMERecord;
@@ -73,7 +74,9 @@ public class Messages {
       sb.append(simplePrint(record));
       sb.append(" | ");
     }
-    sb.delete(sb.length() - 3, sb.length());
+    if (sb.length() > 3) {
+      sb.delete(sb.length() - 3, sb.length());
+    }
     return sb.toString();
   }
 
@@ -102,8 +105,11 @@ public class Messages {
     return aAnswer(query, ip, DEFAULT_TTL);
   }
 
-  public static Message aAnswer(Message query, String ip, final long ttl) {
+  public static Message aAnswer(Message query, String ip, long ttl) {
     final var res = withNoErrorResponse(query.clone());
+    if(StringUtils.isBlank(ip)){
+      return res;
+    }
     final var answer = new ARecord(res.getQuestion().getName(), DClass.IN, ttl, Ips.toAddress(ip));
     res.addRecord(answer, Section.ANSWER);
     return res;
@@ -139,8 +145,8 @@ public class Messages {
   }
 
   @SneakyThrows
-  public static Record query(String host, final int type) {
-    return Record.newRecord(Name.fromString(host), type, DClass.IN, 0);
+  public static Record query(final String host, final int type) {
+    return Record.newRecord(Name.fromString(Hostnames.toAbsoluteName(host)), type, DClass.IN, 0);
   }
 
   public static Integer findQuestionTypeCode(Message msg) {
@@ -220,8 +226,11 @@ public class Messages {
     return quadAnswer(query, ip, DEFAULT_TTL);
   }
 
-  public static Message quadAnswer(Message query, String ip, final long ttl) {
+  public static Message quadAnswer(final Message query, final String ip, final long ttl) {
     final var res = withNoErrorResponse(query.clone());
+    if (StringUtils.isBlank(ip)) {
+      return res;
+    }
     final var answer = new AAAARecord(res.getQuestion().getName(), DClass.IN, ttl, Ips.toAddress(ip));
     res.addRecord(answer, Section.ANSWER);
     return res;
@@ -235,10 +244,14 @@ public class Messages {
   }
 
   public static Message answer(Message query, String ip, IP.Version version) {
+    return answer(query, ip, version, DEFAULT_TTL);
+  }
+
+  public static Message answer(Message query, String ip, IP.Version version, long ttl) {
     if (version.isIpv6()) {
-      return Messages.quadAnswer(query, ip);
+      return Messages.quadAnswer(query, ip, ttl);
     }
-    return Messages.aAnswer(query, ip);
+    return Messages.aAnswer(query, ip, ttl);
   }
 
   static Message withNoErrorResponse(Message res) {
@@ -279,4 +292,5 @@ public class Messages {
     final var version = Entry.Type.of(findQuestionTypeCode(query)).toVersion();
     return HostnameQuery.of(host, version);
   }
+
 }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/HostnameQuery.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/HostnameQuery.java
index 4223a8b0..8adb391c 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/HostnameQuery.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/HostnameQuery.java
@@ -1,5 +1,6 @@
 package com.mageddo.dnsproxyserver.server.dns.solver;
 
+import com.mageddo.dnsproxyserver.config.Config.Entry.Type;
 import com.mageddo.dnsproxyserver.server.dns.Hostname;
 import com.mageddo.dnsproxyserver.server.dns.Wildcards;
 import com.mageddo.net.IP;
@@ -26,6 +27,14 @@ public class HostnameQuery {
 
   private final boolean useRegex;
 
+  public Type getType() {
+    return switch (this.version) {
+      case IPV4 -> Type.A;
+      case IPV6 -> Type.AAAA;
+      default -> throw new UnsupportedOperationException("Invalid version: " + this.version);
+    };
+  }
+
   public static HostnameQuery of(Hostname hostname) {
     return of(hostname, false, false);
   }
@@ -94,6 +103,10 @@ public class HostnameQuery {
       .build();
   }
 
+  public static HostnameQuery of(String hostname, IP.Version version) {
+    return of(Hostname.of(hostname), version);
+  }
+
   public boolean matches(Hostname hostname) {
     return matches(hostname.getCanonicalValue());
   }
@@ -116,4 +129,12 @@ public class HostnameQuery {
     }
     return this.hostname.isEqualTo(hostnamePattern);
   }
+
+  public boolean matches(HostnameQuery actual) {
+    return this.matches(actual.getHostname()) && this.getVersion() == actual.getVersion();
+  }
+
+  public boolean isTypeEqualTo(Type type) {
+    return this.getType() == type;
+  }
 }
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 f5a50757..9b89bda5 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
@@ -1,6 +1,5 @@
 package com.mageddo.dnsproxyserver.server.dns.solver;
 
-import com.mageddo.commons.lang.Objects;
 import com.mageddo.dnsproxyserver.config.Config.Entry.Type;
 import com.mageddo.dnsproxyserver.docker.ContainerSolvingService;
 import com.mageddo.dnsproxyserver.docker.DockerDAO;
@@ -37,11 +36,15 @@ public class SolverDocker implements Solver {
     final var askedHost = Messages.findQuestionHostname(query);
     final var version = Type.of(type).toVersion();
     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()))
-      );
+      final var entry = this.containerSolvingService.findBestMatch(hostname);
+      if (!entry.isHostnameMatched()) {
+        return null;
+      }
+      return Response.of(Messages.answer(
+        query,
+        entry.getIpText(),
+        hostname.getVersion()
+      ));
     });
 
   }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDB.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDB.java
index e5a38682..186c4169 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDB.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDB.java
@@ -4,7 +4,6 @@ import com.mageddo.dnsproxyserver.config.Config;
 import com.mageddo.dnsproxyserver.config.Config.Entry.Type;
 import com.mageddo.dnsproxyserver.config.ConfigDAO;
 import com.mageddo.dnsproxyserver.server.dns.Messages;
-import com.mageddo.net.IP;
 import dagger.Lazy;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -35,10 +34,11 @@ public class SolverLocalDB implements Solver {
     }
 
     final var askedHost = Messages.findQuestionHostname(query);
-    final var res = HostnameMatcher.match(askedHost, IP.Version.IPV4, hostname -> {
+    final var questionType = Messages.findQuestionType(query);
+    final var res = HostnameMatcher.match(askedHost, questionType.toVersion(), hostname -> {
       stopWatch.split();
-      final var entry = this.findEntryTo(hostname);
-      if (entry == null) {
+      final var found = this.findEntryTo(hostname);
+      if (found == null) {
         log.trace(
           "status=partialNotFound, askedHost={}, time={}",
           askedHost, stopWatch.getTime() - stopWatch.getSplitTime()
@@ -47,16 +47,17 @@ public class SolverLocalDB implements Solver {
       }
       log.trace(
         "status=found, type={}, askedHost={}, time={}, totalTime={}",
-        entry.getType(), askedHost, stopWatch.getTime() - stopWatch.getSplitTime(), stopWatch.getTime()
+        found.getType(), askedHost, stopWatch.getTime() - stopWatch.getSplitTime(), stopWatch.getTime()
       );
 
-      if (Type.is(entry.getType(), Type.A, Type.AAAA)) {
+      if (found.getType().isAddressSolving()) {
+        final var ip = found.getType().equals(questionType) ? found.getIp() : null;
         return Response.of(
-          Messages.aAnswer(query, entry.getIp(), entry.getTtl()),
-          Duration.ofSeconds(entry.getTtl())
+          Messages.answer(query, ip, questionType.toVersion(), found.getTtl()),
+          Duration.ofSeconds(found.getTtl())
         );
       }
-      return this.solverDelegate.get().solve(query, entry);
+      return this.solverDelegate.get().solve(query, found);
     });
     if (res != null) {
       return res;
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverSystem.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverSystem.java
index 37514ab6..658c1ea0 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverSystem.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverSystem.java
@@ -1,9 +1,11 @@
 package com.mageddo.dnsproxyserver.server.dns.solver;
 
+import com.mageddo.commons.lang.Objects;
 import com.mageddo.dnsproxyserver.config.Config.Entry.Type;
 import com.mageddo.dnsproxyserver.config.Configs;
 import com.mageddo.dnsproxyserver.server.dns.Messages;
 import com.mageddo.dnsproxyserver.usecase.HostMachineService;
+import com.mageddo.net.IP;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.xbill.DNS.Message;
@@ -29,13 +31,9 @@ public class SolverSystem implements Solver {
     final var config = Configs.getInstance();
     if (hostname.isEqualTo(config.getHostMachineHostname())) { // fixme fazer case com hostname + search domain
       final var ip = this.machineService.findHostMachineIP(questionType.toVersion());
-      if (ip == null) {
-        log.debug("status=hostMachineIpNotFound, host={}", hostname);
-        return null;
-      }
       log.debug("status=solvingHostMachineName, host={}, ip={}", hostname, ip);
       return Response.of(
-        Messages.answer(query, ip.toText(), questionType.toVersion()),
+        Messages.answer(query, Objects.mapOrNull(ip, IP::toText), questionType.toVersion()),
         Messages.DEFAULT_TTL_DURATION
       );
     }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/utils/Ips.java b/src/main/java/com/mageddo/dnsproxyserver/utils/Ips.java
index 566c01ac..80063a6c 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/utils/Ips.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/utils/Ips.java
@@ -31,6 +31,9 @@ public class Ips {
 
   public static InetAddress toAddress(String ip) {
     try {
+      if (StringUtils.isBlank(ip)) {
+        return null;
+      }
       return InetAddress.getByName(ip);
     } catch (UnknownHostException e) {
       throw new RuntimeException(e);


Test patch:
diff --git a/src/test/java/com/mageddo/dnsproxyserver/config/ConfigDAOJsonTest.java b/src/test/java/com/mageddo/dnsproxyserver/config/ConfigDAOJsonTest.java
index 0c6dd800..26d0d1f0 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/config/ConfigDAOJsonTest.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/config/ConfigDAOJsonTest.java
@@ -1,7 +1,9 @@
 package com.mageddo.dnsproxyserver.config;
 
+import com.mageddo.dnsproxyserver.config.Config.Entry.Type;
 import com.mageddo.dnsproxyserver.server.dns.solver.HostnameQuery;
 import com.mageddo.dnsproxyserver.templates.EnvTemplates;
+import com.mageddo.dnsproxyserver.templates.HostnameQueryTemplates;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.InjectMocks;
@@ -37,4 +39,59 @@ class ConfigDAOJsonTest {
     assertNotNull(entry);
     assertEquals(MAGEDDO_COM_CAMEL_CASE, entry.getHostname());
   }
+
+  @Test
+  void mustSolveQuadARecord(){
+    // arrange
+    final var query = HostnameQueryTemplates.acmeComQuadA();
+
+    final var env = EnvTemplates.acmeQuadA();
+    doReturn(env)
+      .when(this.dao)
+      .findActiveEnv();
+
+    // act
+    final var found = this.dao.findEntryForActiveEnv(query);
+
+    // assert
+    assertNotNull(found);
+  }
+
+  @Test
+  void mustSolveQueriedTypeWhenTwoDifferentTypesAreAvailable(){
+    // arrange
+    final var query = HostnameQueryTemplates.acmeComQuadA();
+
+    final var env = EnvTemplates.acmeAAndQuadA();
+    doReturn(env)
+      .when(this.dao)
+      .findActiveEnv();
+
+    // act
+    final var found = this.dao.findEntryForActiveEnv(query);
+
+    // assert
+    assertNotNull(found);
+    assertEquals(Type.AAAA, found.getType());
+  }
+
+
+  @Test
+  void mustReturnWhatExistsWhenNotBestMatchIsFound(){
+    // arrange
+    final var query = HostnameQueryTemplates.acmeComQuadA();
+
+    final var env = EnvTemplates.acmeCname();
+    doReturn(env)
+      .when(this.dao)
+      .findActiveEnv();
+
+    // act
+    final var found = this.dao.findEntryForActiveEnv(query);
+
+    // assert
+    assertNotNull(found);
+    assertEquals(Type.CNAME, found.getType());
+  }
+
 }
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 423ef85b..c64381fd 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
@@ -1,10 +1,12 @@
 package com.mageddo.dnsproxyserver.server.dns.solver;
 
+import com.mageddo.dnsproxyserver.config.Config.Entry.Type;
 import com.mageddo.dnsproxyserver.docker.ContainerSolvingService;
 import com.mageddo.dnsproxyserver.docker.DockerDAO;
+import com.mageddo.dnsproxyserver.server.dns.Messages;
 import com.mageddo.dnsproxyserver.templates.HostnameTemplates;
-import com.mageddo.dnsproxyserver.templates.IpTemplates;
 import com.mageddo.dnsproxyserver.templates.MessageTemplates;
+import com.mageddo.dnsproxyserver.templates.docker.EntryTemplates;
 import com.mageddo.net.IP;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -17,6 +19,7 @@ import org.xbill.DNS.Flags;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+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.ArgumentMatchers.eq;
@@ -46,16 +49,16 @@ class SolverDockerTest {
   void mustSolveExactHostname() {
     // arrange
     final var query = MessageTemplates.acmeAQuery();
-    final var ip = "0.0.0.0";
+    final var entry = EntryTemplates.zeroIp();
     final var hostname = HostnameQuery.ofWildcard(HostnameTemplates.ACME_HOSTNAME);
 
     doReturn(true)
       .when(this.dockerDAO)
       .isConnected()
     ;
-    doReturn(ip)
+    doReturn(entry)
       .when(this.containerSolvingService)
-      .findBestHostIP(eq(hostname));
+      .findBestMatch(eq(hostname));
 
     // act
     final var res = this.solver.handle(query);
@@ -64,25 +67,24 @@ class SolverDockerTest {
     assertNotNull(res);
 
     final var resText = res.toString();
-    assertTrue(resText.contains(ip), resText);
-    verify(this.containerSolvingService).findBestHostIP(hostname);
+    assertTrue(resText.contains(entry.getIp().toText()), resText);
+    verify(this.containerSolvingService).findBestMatch(hostname);
   }
 
-
   @Test
   void mustSolveQuadARecordQuery() {
     // arrange
     final var query = MessageTemplates.acmeQuadAQuery();
-    final var ip = IpTemplates.LOCAL_EXTENDED_IPV6;
+    final var entry = EntryTemplates.localIpv6();
 
     doReturn(true)
       .when(this.dockerDAO)
       .isConnected()
     ;
 
-    doReturn(ip)
+    doReturn(entry)
       .when(this.containerSolvingService)
-      .findBestHostIP(any());
+      .findBestMatch(any());
 
     // act
     final var res = this.solver.handle(query);
@@ -91,11 +93,58 @@ class SolverDockerTest {
     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());
+    assertTrue(resText.contains(entry.getIp().toText()), resText);
+    verify(this.containerSolvingService).findBestMatch(this.hostnameQueryCaptor.capture());
 
     final var v = this.hostnameQueryCaptor.getValue();
     assertEquals(IP.Version.IPV6, v.getVersion());
   }
 
+  @Test
+  void mustSolveEmptyIpWhenHostnameMatchesButNoIpIsFound() {
+    // arrange
+    final var query = MessageTemplates.acmeQuadAQuery();
+    final var entry = EntryTemplates.hostnameMatchedButNoAddress();
+
+    doReturn(true)
+      .when(this.dockerDAO)
+      .isConnected()
+    ;
+
+    doReturn(entry)
+      .when(this.containerSolvingService)
+      .findBestMatch(any());
+
+    // act
+    final var res = this.solver.handle(query);
+
+    // assert
+    assertNotNull(res);
+    assertTrue(Responses.hasFlag(res, Flags.RA));
+    assertEquals(Type.AAAA, Messages.findQuestionType(res.getMessage()));
+    assertEquals("", Messages.detailedPrint(res.getMessage()));
+  }
+
+  @Test
+  void mustReturnNxDomainWhenHostnameDONTMatches() {
+    // arrange
+    final var query = MessageTemplates.acmeQuadAQuery();
+    final var entry = EntryTemplates.hostnameNotMatched();
+
+    doReturn(true)
+      .when(this.dockerDAO)
+      .isConnected()
+    ;
+
+    doReturn(entry)
+      .when(this.containerSolvingService)
+      .findBestMatch(any());
+
+    // act
+    final var res = this.solver.handle(query);
+
+    // assert
+    assertNull(res);
+  }
+
 }
diff --git a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDBCompTest.java b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDBCompTest.java
index 69f79ca1..e7cc366f 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDBCompTest.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDBCompTest.java
@@ -97,12 +97,11 @@ class SolverLocalDBCompTest {
 
   }
 
-
   @Test
   void mustSolveAAARecordAsAFromLocalDB() {
 
     // arrange
-    this.configDAO.addEntry(Config.Env.DEFAULT_ENV, EntryTemplates.acmeAAAA());
+    this.configDAO.addEntry(Config.Env.DEFAULT_ENV, EntryTemplates.acmeQuadA());
     final var msg = Messages.quadAQuestion(toAbsoluteName(EntryTemplates.ACME_COM));
 
     // act
@@ -111,7 +110,7 @@ class SolverLocalDBCompTest {
     // assert
     assertNotNull(res);
     assertEquals(
-      "acme.com.    45  IN  A  10.10.0.1",
+      "acme.com.    45  IN  AAAA  2001:db8:1:0:0:0:0:2",
       Messages.detailedPrint(res.getMessage())
     );
 
diff --git a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDBTest.java b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDBTest.java
index 819c5f1d..ff58646a 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDBTest.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDBTest.java
@@ -4,11 +4,15 @@ import com.mageddo.dnsproxyserver.server.dns.Messages;
 import com.mageddo.dnsproxyserver.templates.EntryTemplates;
 import com.mageddo.dnsproxyserver.templates.HostnameTemplates;
 import com.mageddo.dnsproxyserver.templates.MessageTemplates;
+import com.mageddo.net.IP;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.InjectMocks;
 import org.mockito.Spy;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.xbill.DNS.Rcode;
+
+import java.time.Duration;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -32,7 +36,7 @@ class SolverLocalDBTest {
     final var hostname = HostnameQuery.of(HostnameTemplates.ACME_HOSTNAME);
     final var wildcardHostName = HostnameQuery.ofWildcard(hostname.getHostname());
 
-    doReturn(EntryTemplates.acmeAAAA())
+    doReturn(EntryTemplates.acmeA())
       .when(this.solver)
       .findEntryTo(eq(wildcardHostName))
     ;
@@ -46,11 +50,59 @@ class SolverLocalDBTest {
       "acme.com.    45  IN  A  10.10.0.1",
       Messages.detailedPrint(res.getMessage())
     );
+    assertEquals(Duration.ofSeconds(45), res.getTtl());
 
     verify(this.solver, never()).findEntryTo(hostname);
     verify(this.solver).findEntryTo(wildcardHostName);
 
   }
 
+  @Test
+  void mustSolveQuadAQueries() {
+
+    // arrange
+    final var query = MessageTemplates.acmeQuadAQuery();
+    final var wildcardHostName = HostnameQuery.ofWildcard(HostnameTemplates.ACME_HOSTNAME, IP.Version.IPV6);
+
+    doReturn(EntryTemplates.acmeQuadA())
+      .when(this.solver)
+      .findEntryTo(eq(wildcardHostName))
+    ;
+
+    // act
+    final var res = this.solver.handle(query);
+
+    // assert
+    assertNotNull(res);
+    assertEquals(
+      "acme.com.    45  IN  AAAA  2001:db8:1:0:0:0:0:2",
+      Messages.detailedPrint(res.getMessage())
+    );
+
+    verify(this.solver).findEntryTo(wildcardHostName);
+
+  }
+
+  @Test
+  void mustSolveNoErrorWhenHostIsFoundButAddressIsNot() {
+
+    // arrange
+    final var query = MessageTemplates.acmeQuadAQuery();
+    final var wildcardHostName = HostnameQuery.ofWildcard(HostnameTemplates.ACME_HOSTNAME, IP.Version.IPV6);
+
+    doReturn(EntryTemplates.acmeA())
+      .when(this.solver)
+      .findEntryTo(eq(wildcardHostName))
+    ;
+
+    // act
+    final var res = this.solver.handle(query);
 
+    // assert
+    assertNotNull(res);
+    final var msg = res.getMessage();
+    assertEquals(Rcode.NOERROR, msg.getRcode());
+    assertEquals("", Messages.detailedPrint(msg));
+
+  }
 }
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 dd604be3..5639c86a 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
@@ -1,6 +1,7 @@
 package com.mageddo.dnsproxyserver.server.dns.solver;
 
 import com.mageddo.dnsproxyserver.server.dns.Messages;
+import com.mageddo.dnsproxyserver.templates.HostnameTemplates;
 import com.mageddo.dnsproxyserver.usecase.HostMachineService;
 import com.mageddo.net.IP;
 import dagger.sheath.InjectMock;
@@ -8,6 +9,7 @@ import dagger.sheath.junit.DaggerTest;
 import org.hamcrest.CoreMatchers;
 import org.junit.jupiter.api.Test;
 import org.xbill.DNS.Flags;
+import org.xbill.DNS.Rcode;
 import testing.ContextSupplier;
 import testing.Events;
 
@@ -15,6 +17,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.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -31,9 +34,9 @@ class SolverSystemCompTest {
   SolverSystem solver;
 
   @Test
-  void mustSolverHostMachineIp(){
+  void mustSolverHostMachineIp() {
     // arrange
-    final var hostname = "host.docker.";
+    final var hostname = HostnameTemplates.HOST_DOCKER;
     final var query = Messages.aQuestion(hostname);
 
     doReturn(IP.of("192.168.0.1"))
@@ -54,4 +57,21 @@ class SolverSystemCompTest {
     verify(this.machineService).findHostMachineIP(eq(IP.Version.IPV4));
   }
 
+  @Test
+  void mustSolveNoErrorWhenHostIsFoundButAddressIsNot() {
+
+    // arrange
+    final var query = Messages.quadAQuestion(HostnameTemplates.HOST_DOCKER);
+
+    // act
+    final var res = this.solver.handle(query);
+
+    // assert
+    assertNotNull(res);
+    final var msg = res.getMessage();
+    assertEquals(Rcode.NOERROR, msg.getRcode());
+    assertEquals("", Messages.detailedPrint(msg));
+
+  }
+
 }
diff --git a/src/test/java/com/mageddo/dnsproxyserver/templates/EntryTemplates.java b/src/test/java/com/mageddo/dnsproxyserver/templates/EntryTemplates.java
index aec98513..2c74794b 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/templates/EntryTemplates.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/templates/EntryTemplates.java
@@ -33,10 +33,19 @@ public class EntryTemplates {
     return a(host)
       .toBuilder()
       .type(Type.AAAA)
+      .ip(IpTemplates.LOCAL_IPV6)
       .build();
   }
 
-  public static Config.Entry acmeAAAA() {
+  public static Config.Entry acmeQuadA() {
     return aaaa(ACME_COM);
   }
+
+  public static Config.Entry acmeA() {
+    return a(ACME_COM);
+  }
+
+  public static Config.Entry acmeCname() {
+    return cname(ACME_COM, HostnameTemplates.ORANGE_ACME_HOSTNAME);
+  }
 }
diff --git a/src/test/java/com/mageddo/dnsproxyserver/templates/EnvTemplates.java b/src/test/java/com/mageddo/dnsproxyserver/templates/EnvTemplates.java
index fecd72be..86005be6 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/templates/EnvTemplates.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/templates/EnvTemplates.java
@@ -30,4 +30,26 @@ public class EnvTemplates {
         .build()
       );
   }
+  public static Config.Env acmeQuadA(){
+    return Config.Env.theDefault()
+      .add(EntryTemplates.acmeQuadA());
+  }
+
+  public static Config.Env acmeA(){
+    return Config.Env.theDefault()
+      .add(EntryTemplates.acmeA());
+  }
+
+  public static Config.Env acmeAAndQuadA(){
+    return Config.Env.theDefault()
+      .add(EntryTemplates.acmeA())
+      .add(EntryTemplates.acmeQuadA())
+      ;
+  }
+
+  public static Config.Env acmeCname() {
+    return Config.Env.theDefault()
+      .add(EntryTemplates.acmeCname())
+      ;
+  }
 }
diff --git a/src/test/java/com/mageddo/dnsproxyserver/templates/HostnameQueryTemplates.java b/src/test/java/com/mageddo/dnsproxyserver/templates/HostnameQueryTemplates.java
index 7f42c17f..24395abe 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/templates/HostnameQueryTemplates.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/templates/HostnameQueryTemplates.java
@@ -1,6 +1,7 @@
 package com.mageddo.dnsproxyserver.templates;
 
 import com.mageddo.dnsproxyserver.server.dns.solver.HostnameQuery;
+import com.mageddo.net.IP;
 
 public class HostnameQueryTemplates {
 
@@ -20,6 +21,10 @@ public class HostnameQueryTemplates {
     return HostnameQuery.ofWildcard("acme.com.local");
   }
 
+  public static HostnameQuery acmeComQuadA() {
+    return HostnameQuery.of(HostnameTemplates.ACME_HOSTNAME, IP.Version.IPV6);
+  }
+
   public static HostnameQuery nginxComBrWildcard() {
     return HostnameQuery.ofWildcard(HostnameTemplates.NGINX_COM_BR);
   }
diff --git a/src/test/java/com/mageddo/dnsproxyserver/templates/HostnameTemplates.java b/src/test/java/com/mageddo/dnsproxyserver/templates/HostnameTemplates.java
index 507d4375..a103ccc8 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/templates/HostnameTemplates.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/templates/HostnameTemplates.java
@@ -5,4 +5,5 @@ public class HostnameTemplates {
   public static final String ACME_HOSTNAME = "acme.com";
   public static final String COM_WILDCARD = ".com";
   public static final String NGINX_COM_BR = "nginx.com.br";
+  public static final String HOST_DOCKER = "host.docker";
 }
diff --git a/src/test/java/com/mageddo/dnsproxyserver/templates/IpTemplates.java b/src/test/java/com/mageddo/dnsproxyserver/templates/IpTemplates.java
index 9c47d47c..52edf533 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/templates/IpTemplates.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/templates/IpTemplates.java
@@ -8,6 +8,7 @@ public class IpTemplates {
   public static final String LOCAL_IPV6 = "2001:db8:1::2";
   public static final String LOCAL_EXTENDED_IPV6 = "2001:db8:1:0:0:0:0:2";
   public static final String LOCAL_192 = "192.168.0.10";
+  public static final String ZERO = "0.0.0.0";
 
   public static IP local(){
     return IP.of(LOCAL);
diff --git a/src/test/java/com/mageddo/dnsproxyserver/templates/docker/EntryTemplates.java b/src/test/java/com/mageddo/dnsproxyserver/templates/docker/EntryTemplates.java
new file mode 100644
index 00000000..7dfce9cb
--- /dev/null
+++ b/src/test/java/com/mageddo/dnsproxyserver/templates/docker/EntryTemplates.java
@@ -0,0 +1,38 @@
+package com.mageddo.dnsproxyserver.templates.docker;
+
+import com.mageddo.dnsproxyserver.docker.Entry;
+import com.mageddo.dnsproxyserver.templates.IpTemplates;
+import com.mageddo.net.IP;
+
+public class EntryTemplates {
+  public static Entry zeroIp() {
+    return Entry
+      .builder()
+      .ip(IP.of(IpTemplates.ZERO))
+      .hostnameMatched(true)
+      .build();
+  }
+
+  public static Entry localIpv6() {
+    return Entry
+      .builder()
+      .hostnameMatched(true)
+      .ip(IP.of(IpTemplates.LOCAL_EXTENDED_IPV6))
+      .build();
+  }
+
+  public static Entry hostnameMatchedButNoAddress() {
+    return Entry
+      .builder()
+      .hostnameMatched(true)
+      .build()
+      ;
+  }
+
+  public static Entry hostnameNotMatched() {
+    return Entry
+      .builder()
+      .build()
+      ;
+  }
+}