diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index bb5b700fc..4b23593df 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,3 +1,6 @@ +## 3.32.1 +* Configuring Stub Solver. #545 + ## 3.32.0 * Implementing Stub Solver but not exposing. #545 diff --git a/docs/content/2-features/stub-solver/_index.en.md b/docs/content/2-features/stub-solver/_index.en.md new file mode 100644 index 000000000..13079fd62 --- /dev/null +++ b/docs/content/2-features/stub-solver/_index.en.md @@ -0,0 +1,48 @@ +--- +title: Stub Solver +--- + +You can use stub solver to transform IPs into hostnames without need to create a +configuration file as in Local DB Solver. Stub Solver was inspired in `nip.io` and `sslip.io`. + +## Examples: + +**Without a name:** + +* `10.0.0.1.stub` => `10.0.0.1` +* `192-168-1-250.stub` => `192.168.1.250` +* `0a000803.stub` => `10.0.8.3` + +**With a name:** + +* `app.10.8.0.1.stub` => `10.8.0.1` +* `app-116-203-255-68.stub` => `116.203.255.68` +* `app-c0a801fc.stub` => `192.168.1.252` +* `customer1.app.10.0.0.1.stub` => `10.0.0.1` +* `customer2-app-127-0-0-1.stub` => `127.0.0.1` +* `customer3-app-7f000101.stub` => `127.0.1.1` +* `app.2a01-4f8-c17-b8f--2.stub` => `2a01:4f8:c17:b8f::2` + +### Format Reference + +``` +${name}[.-]${ip_address}.stub +``` + +* `${name}` (optional): you can set a name to make it easier to recognize the IP +* `${ip_address}`: The ip address which the DNS will answer it can be in 4 formats + * dot notation: magic.127.0.0.1.stub + * dash notation: magic-127-0-0-1.stub + * hexadecimal notation: magic-7f000001.stub + * Ipv6 notation: magic.2a01-4f8-c17-b8f--2.stub + +### Customize the domain name + +<tbd> + +## Refs + +* [Stub Solver #545][1] + + +[1]: https://github.com/mageddo/dns-proxy-server/issues/545 diff --git a/docs/content/3-configuration/_index.en.md b/docs/content/3-configuration/_index.en.md index cce3bfbc0..acb5e1d7a 100755 --- a/docs/content/3-configuration/_index.en.md +++ b/docs/content/3-configuration/_index.en.md @@ -192,15 +192,17 @@ __Version 2__ "dockerSolverHostMachineFallbackActive": true, "solverRemote" : { "circuitBreaker" : { - "failureThreshold" : 3, // how many attempts before open the circuit? - "failureThresholdCapacity" : 10, // how many attempts store to the stack? - "successThreshold" : 5, // how many attempts before close the circuit? - "testDelay" : "PT20S" // how many time to wait before test the circuit again?, see https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#toString-- for format explanation + "failureThreshold" : 3, + "failureThresholdCapacity" : 10, + "successThreshold" : 5, + "testDelay" : "PT20S" } } } ``` +* [Solver remote circuit breaker configuration][3] + ## Environment variable configuration Boolean values @@ -221,3 +223,4 @@ $ docker run defreitas/dns-proxy-server --help [1]: {{%relref "2-features/auto-configuration-as-default-dns/_index.md" %}} [2]: {{%relref "2-features/local-entries/_index.md" %}} +[3]: {{%relref "2-features/remote-solver-circuitbreaker/_index.en.md" %}} diff --git a/gradle.properties b/gradle.properties index 59e3a9861..a2b3f5a99 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=3.32.0-snapshot +version=3.32.1-snapshot diff --git a/src/main/java/com/mageddo/dns/Hostname.java b/src/main/java/com/mageddo/dns/Hostname.java index cc64e013a..876910fee 100644 --- a/src/main/java/com/mageddo/dns/Hostname.java +++ b/src/main/java/com/mageddo/dns/Hostname.java @@ -44,4 +44,8 @@ public String toString() { public static Hostname of(String hostname) { return new Hostname(hostname); } + + public boolean endsWith(String name) { + return this.value.endsWith(StringUtils.lowerCase(name)); + } } diff --git a/src/main/java/com/mageddo/dns/utils/Messages.java b/src/main/java/com/mageddo/dns/utils/Messages.java index eaadfb4a9..e913b341b 100644 --- a/src/main/java/com/mageddo/dns/utils/Messages.java +++ b/src/main/java/com/mageddo/dns/utils/Messages.java @@ -269,7 +269,11 @@ public static Message withResponseCode(Message res, int rRode) { return res; } - static Message withDefaultResponseHeaders(Message res) { + public static int getRCode(Message m){ + return m.getRcode(); + } + + public static Message withDefaultResponseHeaders(Message res) { final var header = res.getHeader(); header.setFlag(Flags.QR); header.setFlag(Flags.RA); @@ -305,4 +309,8 @@ public static boolean isSuccess(Message res) { public static String findAnswerRawIP(Message res) { return findFirstAnswerRecord(res).rdataToString(); } + + public static boolean isNxDomain(Message m) { + return m.getRcode() == Rcode.NXDOMAIN; + } } diff --git a/src/main/java/com/mageddo/dnsproxyserver/di/module/ModuleSolver.java b/src/main/java/com/mageddo/dnsproxyserver/di/module/ModuleSolver.java index db3b1461e..ff64c12ec 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/di/module/ModuleSolver.java +++ b/src/main/java/com/mageddo/dnsproxyserver/di/module/ModuleSolver.java @@ -9,6 +9,7 @@ import com.mageddo.dnsproxyserver.solver.SolverDocker; import com.mageddo.dnsproxyserver.solver.SolverLocalDB; import com.mageddo.dnsproxyserver.solver.SolverSystem; +import com.mageddo.dnsproxyserver.solver.stub.SolverStub; import dagger.Module; import dagger.Provides; import dagger.multibindings.ElementsIntoSet; @@ -24,9 +25,9 @@ public interface ModuleSolver { @Singleton @ElementsIntoSet static Set solvers( - SolverSystem o1, SolverDocker o2, SolverLocalDB o3, SolverCachedRemote o4 + SolverSystem o1, SolverDocker o2, SolverLocalDB o3, SolverCachedRemote o4, SolverStub o5 ) { - return Set.of(o1, o2, o3, o4); + return Set.of(o1, o2, o3, o4, o5); } @Provides diff --git a/src/main/java/com/mageddo/dnsproxyserver/solver/Response.java b/src/main/java/com/mageddo/dnsproxyserver/solver/Response.java index de6437c4c..d9cc558c0 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/solver/Response.java +++ b/src/main/java/com/mageddo/dnsproxyserver/solver/Response.java @@ -5,6 +5,7 @@ import lombok.NonNull; import lombok.Value; import org.xbill.DNS.Message; +import org.xbill.DNS.Rcode; import java.time.Duration; import java.time.LocalDateTime; @@ -50,6 +51,9 @@ public static Response of(Message message, Duration dpsTtl) { } public static Response nxDomain(Message message) { + if (!Messages.isNxDomain(message)) { + Messages.withResponseCode(message, Rcode.NXDOMAIN); + } return of(message, DEFAULT_NXDOMAIN_TTL); } @@ -74,4 +78,8 @@ public Response withTTL(Duration ttl) { .dpsTtl(ttl) .build(); } + + public int getRCode() { + return this.message.getRcode(); + } } diff --git a/src/main/java/com/mageddo/dnsproxyserver/solver/ResponseMapper.java b/src/main/java/com/mageddo/dnsproxyserver/solver/ResponseMapper.java new file mode 100644 index 000000000..3d67fe4cd --- /dev/null +++ b/src/main/java/com/mageddo/dnsproxyserver/solver/ResponseMapper.java @@ -0,0 +1,15 @@ +package com.mageddo.dnsproxyserver.solver; + +import com.mageddo.commons.lang.Objects; +import com.mageddo.dns.utils.Messages; +import com.mageddo.net.IP; +import org.xbill.DNS.Message; + +public class ResponseMapper { + public static Response toDefaultSuccessAnswer(Message query, IP ip, IP.Version version) { + return Response.of( + Messages.answer(query, Objects.mapOrNull(ip, IP::toText), version), + Messages.DEFAULT_TTL_DURATION + ); + } +} diff --git a/src/main/java/com/mageddo/dnsproxyserver/solver/SolverProvider.java b/src/main/java/com/mageddo/dnsproxyserver/solver/SolverProvider.java index 44d7532cc..e8478693e 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/solver/SolverProvider.java +++ b/src/main/java/com/mageddo/dnsproxyserver/solver/SolverProvider.java @@ -17,6 +17,7 @@ public class SolverProvider { static final String[] solversOrder = { "SolverSystem", + "SolverStub", "SolverDocker", SolverLocalDB.NAME, SolverCachedRemote.NAME diff --git a/src/main/java/com/mageddo/dnsproxyserver/solver/SolverSystem.java b/src/main/java/com/mageddo/dnsproxyserver/solver/SolverSystem.java index 5f7ce3167..c2e71855f 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/solver/SolverSystem.java +++ b/src/main/java/com/mageddo/dnsproxyserver/solver/SolverSystem.java @@ -1,12 +1,10 @@ package com.mageddo.dnsproxyserver.solver; -import com.mageddo.commons.lang.Objects; +import com.mageddo.dns.utils.Messages; import com.mageddo.dnsproxyserver.config.Config.Entry.Type; -import com.mageddo.dnsproxyserver.config.application.Configs; import com.mageddo.dnsproxyserver.config.ConfigEntryTypes; -import com.mageddo.dns.utils.Messages; +import com.mageddo.dnsproxyserver.config.application.Configs; import com.mageddo.dnsproxyserver.usecase.HostMachineService; -import com.mageddo.net.IP; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.xbill.DNS.Message; @@ -36,10 +34,7 @@ public Response handle(Message query) { if (hostname.isEqualTo(config.getHostMachineHostname())) { // fixme fazer case com hostname + search domain final var ip = this.machineService.findHostMachineIP(questionType.toVersion()); log.debug("status=solvingHostMachineName, host={}, ip={}", hostname, ip); - return Response.of( - Messages.answer(query, Objects.mapOrNull(ip, IP::toText), questionType.toVersion()), - Messages.DEFAULT_TTL_DURATION - ); + return ResponseMapper.toDefaultSuccessAnswer(query, ip, questionType.toVersion()); } return null; } diff --git a/src/main/java/com/mageddo/dnsproxyserver/solver/stub/HostnameIpExtractor.java b/src/main/java/com/mageddo/dnsproxyserver/solver/stub/HostnameIpExtractor.java index 1a5440e75..13d5852bb 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/solver/stub/HostnameIpExtractor.java +++ b/src/main/java/com/mageddo/dnsproxyserver/solver/stub/HostnameIpExtractor.java @@ -1,12 +1,28 @@ package com.mageddo.dnsproxyserver.solver.stub; +import com.mageddo.dns.Hostname; import com.mageddo.dnsproxyserver.solver.stub.addressexpression.AddressExpressions; import com.mageddo.dnsproxyserver.solver.stub.addressexpression.ParseException; import com.mageddo.net.IP; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.Validate; +@Slf4j public class HostnameIpExtractor { + public static IP safeExtract(Hostname hostname, String domain) { + try { + return extract(hostname, domain); + } catch (Exception e) { + log.info("status=failedToExtractIpFromHostname, hostname={}, msg={}", hostname, e.getMessage(), e); + return null; + } + } + + public static IP extract(Hostname hostname, String domain) { + return extract(hostname.getCanonicalValue(), domain); + } + public static IP extract(String hostname, String domain) { hostname = removeDomainFrom(hostname, domain); diff --git a/src/main/java/com/mageddo/dnsproxyserver/solver/stub/SolverStub.java b/src/main/java/com/mageddo/dnsproxyserver/solver/stub/SolverStub.java index 222fab939..e1ebb7d13 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/solver/stub/SolverStub.java +++ b/src/main/java/com/mageddo/dnsproxyserver/solver/stub/SolverStub.java @@ -1,16 +1,56 @@ package com.mageddo.dnsproxyserver.solver.stub; +import com.mageddo.dns.utils.Messages; +import com.mageddo.dnsproxyserver.config.Config; +import com.mageddo.dnsproxyserver.config.ConfigEntryTypes; import com.mageddo.dnsproxyserver.solver.Response; +import com.mageddo.dnsproxyserver.solver.ResponseMapper; import com.mageddo.dnsproxyserver.solver.Solver; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.xbill.DNS.Message; +import javax.inject.Inject; +import javax.inject.Singleton; + +import static com.mageddo.dns.utils.Messages.findQuestionTypeCode; + /** * Extract the address from the hostname then answer. * Inspired at nip.io and sslip.io, see #545. */ + +@Slf4j +@Singleton +@AllArgsConstructor(onConstructor = @__({@Inject})) public class SolverStub implements Solver { + + public static final String DOMAIN_NAME = "stub"; + @Override public Response handle(Message query) { - return null; + final var questionType = Messages.findQuestionType(query); + if (ConfigEntryTypes.isNot(questionType, Config.Entry.Type.A, Config.Entry.Type.AAAA)) { + log.debug("status=unsupportedType, type={}, query={}", findQuestionTypeCode(query), Messages.simplePrint(query)); + return null; + } + + final var hostname = Messages.findQuestionHostname(query); + if (!hostname.endsWith(DOMAIN_NAME)) { + log.debug("status=hostnameDoesntMatchRequiredDomain, hostname={}", hostname); + return null; + } + + final var foundIp = HostnameIpExtractor.safeExtract(hostname, DOMAIN_NAME); + if (foundIp == null) { + log.debug("status=notSolved, hostname={}", hostname); + return null; + } + if (!foundIp.isVersionEqualsTo(questionType.toVersion())) { + log.debug("status=incompatibleIpAndQueryType, hostname={}, questionType={}", hostname, questionType); + return Response.nxDomain(query); + } + log.debug("status=solved, host={}, ip={}", hostname, foundIp); + return ResponseMapper.toDefaultSuccessAnswer(query, foundIp, questionType.toVersion()); } } diff --git a/src/main/java/com/mageddo/net/IP.java b/src/main/java/com/mageddo/net/IP.java index 3c353d828..692c1453e 100644 --- a/src/main/java/com/mageddo/net/IP.java +++ b/src/main/java/com/mageddo/net/IP.java @@ -37,6 +37,10 @@ static List listOf(String ... ips) { boolean notEqualTo(String ip); + default boolean isVersionEqualsTo(Version version){ + return this.version().equals(version); + } + enum Version { IPV4, diff --git a/src/test/java/com/mageddo/dns/HostnameTest.java b/src/test/java/com/mageddo/dns/HostnameTest.java new file mode 100644 index 000000000..6880382e8 --- /dev/null +++ b/src/test/java/com/mageddo/dns/HostnameTest.java @@ -0,0 +1,21 @@ +package com.mageddo.dns; + +import org.junit.jupiter.api.Test; +import testing.templates.HostnameTemplates; + +import static org.junit.jupiter.api.Assertions.*; + +class HostnameTest { + + @Test + void mustEndsWith(){ + final var hostname = Hostname.of(HostnameTemplates.NGINX_COM_BR); + assertTrue(hostname.endsWith(".com.br")); + } + + @Test + void mustNotEndsWith(){ + final var hostname = Hostname.of(HostnameTemplates.NGINX_COM_BR); + assertFalse(hostname.endsWith(".com")); + } +} diff --git a/src/test/java/com/mageddo/dnsproxyserver/solver/stub/SolverStubTest.java b/src/test/java/com/mageddo/dnsproxyserver/solver/stub/SolverStubTest.java new file mode 100644 index 000000000..b33491902 --- /dev/null +++ b/src/test/java/com/mageddo/dnsproxyserver/solver/stub/SolverStubTest.java @@ -0,0 +1,67 @@ +package com.mageddo.dnsproxyserver.solver.stub; + +import com.mageddo.dns.utils.Messages; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.xbill.DNS.Rcode; +import testing.templates.MessageTemplates; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +@ExtendWith(MockitoExtension.class) +class SolverStubTest { + + @Spy + SolverStub solver; + + @Test + void mustValidateNonSupportedQuestionType(){ + final var query = MessageTemplates.acmeSoaQuery(); + + final var response = this.solver.handle(query); + + assertNull(response); + } + + @Test + void mustValidateIncompatibleDomainName(){ + final var query = MessageTemplates.acmeAQuery(); + + final var response = this.solver.handle(query); + + assertNull(response); + } + + @Test + void mustFindRightIpAddress(){ + final var query = MessageTemplates.dpsStubAQuery(); + + final var response = this.solver.handle(query); + + assertNotNull(response); + assertEquals("192.168.3.1", Messages.findAnswerRawIP(response.getMessage())); + } + + @Test + void willIgnoreHostnameWithRightDomainButNotEmbeddedIp(){ + final var query = MessageTemplates.stubAQueryWithoutIp(); + + final var response = this.solver.handle(query); + + assertNull(response); + } + + @Test + void mustAnswerNxWhenQueryTypeIsNotEqualsToIpVersion(){ + final var query = MessageTemplates.stubAQueryWithIpv6AnswerIp(); + + final var response = this.solver.handle(query); + + assertNotNull(response); + assertEquals(Rcode.NXDOMAIN, response.getRCode()); + } +} diff --git a/src/test/java/testing/templates/MessageTemplates.java b/src/test/java/testing/templates/MessageTemplates.java index f44344882..6e4c854a9 100644 --- a/src/test/java/testing/templates/MessageTemplates.java +++ b/src/test/java/testing/templates/MessageTemplates.java @@ -43,4 +43,16 @@ public static Message buildAAnswerWithoutRA(Message query) { answer.getHeader().unsetFlag(Flags.RA); return answer; } + + public static Message stubAQueryWithoutIp() { + return Messages.aQuestion("dps.stub"); + } + + public static Message dpsStubAQuery() { + return Messages.aQuestion("dps-192.168.3.1.stub"); + } + + public static Message stubAQueryWithIpv6AnswerIp() { + return Messages.aQuestion("dps.a--1.stub"); + } }