From 7b2bf070d0e228cafe7977ab8695d3604dcc4a5a Mon Sep 17 00:00:00 2001 From: imz87 Date: Sun, 23 Jun 2024 12:22:52 +0330 Subject: [PATCH 1/9] feat: add doh required pojo files --- .../core/dns/DnsClientOptionsConverter.java | 6 + .../dns/dnsrecord/DohRecordConverter.java | 129 ++++++++++++++++++ .../dnsrecord/DohResourceRecordConverter.java | 61 +++++++++ .../core/dns/dnsrecord/QuestionConverter.java | 47 +++++++ .../vertx/core/dns/dnsrecord/DohRecord.java | 127 +++++++++++++++++ .../core/dns/dnsrecord/DohResourceRecord.java | 60 ++++++++ .../io/vertx/core/dns/dnsrecord/Question.java | 44 ++++++ 7 files changed, 474 insertions(+) create mode 100644 src/main/generated/io/vertx/core/dns/dnsrecord/DohRecordConverter.java create mode 100644 src/main/generated/io/vertx/core/dns/dnsrecord/DohResourceRecordConverter.java create mode 100644 src/main/generated/io/vertx/core/dns/dnsrecord/QuestionConverter.java create mode 100644 src/main/java/io/vertx/core/dns/dnsrecord/DohRecord.java create mode 100644 src/main/java/io/vertx/core/dns/dnsrecord/DohResourceRecord.java create mode 100644 src/main/java/io/vertx/core/dns/dnsrecord/Question.java diff --git a/src/main/generated/io/vertx/core/dns/DnsClientOptionsConverter.java b/src/main/generated/io/vertx/core/dns/DnsClientOptionsConverter.java index b9eb31a45f1..2dcacc41e3b 100644 --- a/src/main/generated/io/vertx/core/dns/DnsClientOptionsConverter.java +++ b/src/main/generated/io/vertx/core/dns/DnsClientOptionsConverter.java @@ -50,6 +50,11 @@ static void fromJson(Iterable> json, DnsClie obj.setRecursionDesired((Boolean)member.getValue()); } break; + case "ssl": + if (member.getValue() instanceof Boolean) { + obj.setSsl((Boolean)member.getValue()); + } + break; } } } @@ -69,5 +74,6 @@ static void toJson(DnsClientOptions obj, java.util.Map json) { json.put("port", obj.getPort()); json.put("queryTimeout", obj.getQueryTimeout()); json.put("recursionDesired", obj.isRecursionDesired()); + json.put("ssl", obj.isSsl()); } } diff --git a/src/main/generated/io/vertx/core/dns/dnsrecord/DohRecordConverter.java b/src/main/generated/io/vertx/core/dns/dnsrecord/DohRecordConverter.java new file mode 100644 index 00000000000..e334876d1e5 --- /dev/null +++ b/src/main/generated/io/vertx/core/dns/dnsrecord/DohRecordConverter.java @@ -0,0 +1,129 @@ +package io.vertx.core.dns.dnsrecord; + +import io.vertx.core.json.JsonObject; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.impl.JsonUtil; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Base64; + +/** + * Converter and mapper for {@link io.vertx.core.dns.dnsrecord.DohRecord}. + * NOTE: This class has been automatically generated from the {@link io.vertx.core.dns.dnsrecord.DohRecord} original class using Vert.x codegen. + */ +public class DohRecordConverter { + + + private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; + private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; + + static void fromJson(Iterable> json, DohRecord obj) { + for (java.util.Map.Entry member : json) { + switch (member.getKey()) { + case "ad": + if (member.getValue() instanceof Boolean) { + obj.setAd((Boolean)member.getValue()); + } + break; + case "additional": + if (member.getValue() instanceof JsonArray) { + java.util.ArrayList list = new java.util.ArrayList<>(); + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof JsonObject) + list.add(new io.vertx.core.dns.dnsrecord.DohResourceRecord((io.vertx.core.json.JsonObject)item)); + }); + obj.setAdditional(list); + } + break; + case "answer": + if (member.getValue() instanceof JsonArray) { + java.util.ArrayList list = new java.util.ArrayList<>(); + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof JsonObject) + list.add(new io.vertx.core.dns.dnsrecord.DohResourceRecord((io.vertx.core.json.JsonObject)item)); + }); + obj.setAnswer(list); + } + break; + case "authority": + if (member.getValue() instanceof JsonArray) { + java.util.ArrayList list = new java.util.ArrayList<>(); + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof JsonObject) + list.add(new io.vertx.core.dns.dnsrecord.DohResourceRecord((io.vertx.core.json.JsonObject)item)); + }); + obj.setAuthority(list); + } + break; + case "cd": + if (member.getValue() instanceof Boolean) { + obj.setCd((Boolean)member.getValue()); + } + break; + case "question": + if (member.getValue() instanceof JsonArray) { + java.util.ArrayList list = new java.util.ArrayList<>(); + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof JsonObject) + list.add(new io.vertx.core.dns.dnsrecord.Question((io.vertx.core.json.JsonObject)item)); + }); + obj.setQuestion(list); + } + break; + case "ra": + if (member.getValue() instanceof Boolean) { + obj.setRa((Boolean)member.getValue()); + } + break; + case "rd": + if (member.getValue() instanceof Boolean) { + obj.setRd((Boolean)member.getValue()); + } + break; + case "status": + if (member.getValue() instanceof Number) { + obj.setStatus(((Number)member.getValue()).intValue()); + } + break; + case "tc": + if (member.getValue() instanceof Boolean) { + obj.setTc((Boolean)member.getValue()); + } + break; + } + } + } + + static void toJson(DohRecord obj, JsonObject json) { + toJson(obj, json.getMap()); + } + + static void toJson(DohRecord obj, java.util.Map json) { + json.put("ad", obj.isAd()); + if (obj.getAdditional() != null) { + JsonArray array = new JsonArray(); + obj.getAdditional().forEach(item -> array.add(item.toJson())); + json.put("additional", array); + } + if (obj.getAnswer() != null) { + JsonArray array = new JsonArray(); + obj.getAnswer().forEach(item -> array.add(item.toJson())); + json.put("answer", array); + } + if (obj.getAuthority() != null) { + JsonArray array = new JsonArray(); + obj.getAuthority().forEach(item -> array.add(item.toJson())); + json.put("authority", array); + } + json.put("cd", obj.isCd()); + if (obj.getQuestion() != null) { + JsonArray array = new JsonArray(); + obj.getQuestion().forEach(item -> array.add(item.toJson())); + json.put("question", array); + } + json.put("ra", obj.isRa()); + json.put("rd", obj.isRd()); + json.put("status", obj.getStatus()); + json.put("tc", obj.isTc()); + } +} diff --git a/src/main/generated/io/vertx/core/dns/dnsrecord/DohResourceRecordConverter.java b/src/main/generated/io/vertx/core/dns/dnsrecord/DohResourceRecordConverter.java new file mode 100644 index 00000000000..773e73f3508 --- /dev/null +++ b/src/main/generated/io/vertx/core/dns/dnsrecord/DohResourceRecordConverter.java @@ -0,0 +1,61 @@ +package io.vertx.core.dns.dnsrecord; + +import io.vertx.core.json.JsonObject; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.impl.JsonUtil; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Base64; + +/** + * Converter and mapper for {@link io.vertx.core.dns.dnsrecord.DohResourceRecord}. + * NOTE: This class has been automatically generated from the {@link io.vertx.core.dns.dnsrecord.DohResourceRecord} original class using Vert.x codegen. + */ +public class DohResourceRecordConverter { + + + private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; + private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; + + static void fromJson(Iterable> json, DohResourceRecord obj) { + for (java.util.Map.Entry member : json) { + switch (member.getKey()) { + case "data": + if (member.getValue() instanceof String) { + obj.setData((String)member.getValue()); + } + break; + case "name": + if (member.getValue() instanceof String) { + obj.setName((String)member.getValue()); + } + break; + case "ttl": + if (member.getValue() instanceof Number) { + obj.setTtl(((Number)member.getValue()).intValue()); + } + break; + case "type": + if (member.getValue() instanceof Number) { + obj.setType(((Number)member.getValue()).intValue()); + } + break; + } + } + } + + static void toJson(DohResourceRecord obj, JsonObject json) { + toJson(obj, json.getMap()); + } + + static void toJson(DohResourceRecord obj, java.util.Map json) { + if (obj.getData() != null) { + json.put("data", obj.getData()); + } + if (obj.getName() != null) { + json.put("name", obj.getName()); + } + json.put("ttl", obj.getTtl()); + json.put("type", obj.getType()); + } +} diff --git a/src/main/generated/io/vertx/core/dns/dnsrecord/QuestionConverter.java b/src/main/generated/io/vertx/core/dns/dnsrecord/QuestionConverter.java new file mode 100644 index 00000000000..9e9439f0dee --- /dev/null +++ b/src/main/generated/io/vertx/core/dns/dnsrecord/QuestionConverter.java @@ -0,0 +1,47 @@ +package io.vertx.core.dns.dnsrecord; + +import io.vertx.core.json.JsonObject; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.impl.JsonUtil; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Base64; + +/** + * Converter and mapper for {@link io.vertx.core.dns.dnsrecord.Question}. + * NOTE: This class has been automatically generated from the {@link io.vertx.core.dns.dnsrecord.Question} original class using Vert.x codegen. + */ +public class QuestionConverter { + + + private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; + private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; + + static void fromJson(Iterable> json, Question obj) { + for (java.util.Map.Entry member : json) { + switch (member.getKey()) { + case "name": + if (member.getValue() instanceof String) { + obj.setName((String)member.getValue()); + } + break; + case "type": + if (member.getValue() instanceof Number) { + obj.setType(((Number)member.getValue()).intValue()); + } + break; + } + } + } + + static void toJson(Question obj, JsonObject json) { + toJson(obj, json.getMap()); + } + + static void toJson(Question obj, java.util.Map json) { + if (obj.getName() != null) { + json.put("name", obj.getName()); + } + json.put("type", obj.getType()); + } +} diff --git a/src/main/java/io/vertx/core/dns/dnsrecord/DohRecord.java b/src/main/java/io/vertx/core/dns/dnsrecord/DohRecord.java new file mode 100644 index 00000000000..deae9a8db98 --- /dev/null +++ b/src/main/java/io/vertx/core/dns/dnsrecord/DohRecord.java @@ -0,0 +1,127 @@ +package io.vertx.core.dns.dnsrecord; + +import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; +import io.vertx.core.json.JsonObject; + +import java.util.List; + +@DataObject +@JsonGen(publicConverter = false) +public class DohRecord { + private JsonObject json; + + public DohRecord() { + } + + public DohRecord(JsonObject json) { + DohRecordConverter.fromJson(json, this); + this.json = json.copy(); + } + + public JsonObject toJson() { + JsonObject json = new JsonObject(); + DohRecordConverter.toJson(this, json); + return json; + } + + private int status; + + private boolean tc; + + private boolean rd; + + private boolean ra; + + private boolean ad; + + private boolean cd; + + private List question; + + private List answer; + private List authority; + private List additional; + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public boolean isTc() { + return tc; + } + + public void setTc(boolean tc) { + this.tc = tc; + } + + public boolean isRd() { + return rd; + } + + public void setRd(boolean rd) { + this.rd = rd; + } + + public boolean isRa() { + return ra; + } + + public void setRa(boolean ra) { + this.ra = ra; + } + + public boolean isAd() { + return ad; + } + + public void setAd(boolean ad) { + this.ad = ad; + } + + public boolean isCd() { + return cd; + } + + public void setCd(boolean cd) { + this.cd = cd; + } + + public List getQuestion() { + return question; + } + + public void setQuestion(List question) { + this.question = question; + } + + public List getAnswer() { + return answer; + } + + public void setAnswer(List answer) { + this.answer = answer; + } + + public List getAuthority() { + return authority; + } + + public void setAuthority(List authority) { + this.authority = authority; + } + + public List getAdditional() { + return additional; + } + + public void setAdditional(List additional) { + this.additional = additional; + } +} + + diff --git a/src/main/java/io/vertx/core/dns/dnsrecord/DohResourceRecord.java b/src/main/java/io/vertx/core/dns/dnsrecord/DohResourceRecord.java new file mode 100644 index 00000000000..119aa278056 --- /dev/null +++ b/src/main/java/io/vertx/core/dns/dnsrecord/DohResourceRecord.java @@ -0,0 +1,60 @@ +package io.vertx.core.dns.dnsrecord; + +import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; +import io.vertx.core.json.JsonObject; + +@DataObject +@JsonGen(publicConverter = false) +public class DohResourceRecord { + private JsonObject json; + + public DohResourceRecord() { + } + + public DohResourceRecord(JsonObject json) { + DohResourceRecordConverter.fromJson(json, DohResourceRecord.this); + this.json = json.copy(); + } + public JsonObject toJson() { + JsonObject json = new JsonObject(); + DohResourceRecordConverter.toJson(this, json); + return json; + } + private String name; + private int type; + private int ttl; + private String data; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getTtl() { + return ttl; + } + + public void setTtl(int ttl) { + this.ttl = ttl; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/src/main/java/io/vertx/core/dns/dnsrecord/Question.java b/src/main/java/io/vertx/core/dns/dnsrecord/Question.java new file mode 100644 index 00000000000..7a6b10c362a --- /dev/null +++ b/src/main/java/io/vertx/core/dns/dnsrecord/Question.java @@ -0,0 +1,44 @@ +package io.vertx.core.dns.dnsrecord; + +import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; +import io.vertx.core.json.JsonObject; + +@DataObject +@JsonGen(publicConverter = false) +public class Question { + private JsonObject json; + + public Question() { + } + + public Question(JsonObject json) { + QuestionConverter.fromJson(json, this); + this.json = json.copy(); + } + + public JsonObject toJson() { + JsonObject json = new JsonObject(); + QuestionConverter.toJson(this, json); + return json; + } + + private String name; + private int type; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } +} From 6ca214e0daf6f08d43e326126d927aa943221a00 Mon Sep 17 00:00:00 2001 From: imz87 Date: Sun, 23 Jun 2024 13:08:48 +0330 Subject: [PATCH 2/9] feat: add doh core --- src/main/java/io/vertx/core/Vertx.java | 18 ++ .../io/vertx/core/dns/DnsClientOptions.java | 26 +++ .../core/dns/impl/BaseDnsClientImpl.java | 198 ++++++++++++++++ .../io/vertx/core/dns/impl/DnsClientImpl.java | 221 +++--------------- .../io/vertx/core/dns/impl/DohClientImpl.java | 209 +++++++++++++++++ .../dns/impl/decoder/DohRecordDecoder.java | 107 +++++++++ .../core/dns/impl/decoder/JsonHelper.java | 37 +++ .../impl/decoder/StartOfAuthorityRecord.java | 12 + .../java/io/vertx/core/impl/VertxImpl.java | 48 ++-- .../java/io/vertx/core/impl/VertxWrapper.java | 12 +- src/test/java/io/vertx/test/tls/Trust.java | 1 + .../resources/tls/server-keystore-doh.jks | Bin 0 -> 2702 bytes 12 files changed, 676 insertions(+), 213 deletions(-) create mode 100644 src/main/java/io/vertx/core/dns/impl/BaseDnsClientImpl.java create mode 100644 src/main/java/io/vertx/core/dns/impl/DohClientImpl.java create mode 100644 src/main/java/io/vertx/core/dns/impl/decoder/DohRecordDecoder.java create mode 100644 src/main/java/io/vertx/core/dns/impl/decoder/JsonHelper.java create mode 100644 src/test/resources/tls/server-keystore-doh.jks diff --git a/src/main/java/io/vertx/core/Vertx.java b/src/main/java/io/vertx/core/Vertx.java index 3b1f0009e25..cd08c9ea3ec 100644 --- a/src/main/java/io/vertx/core/Vertx.java +++ b/src/main/java/io/vertx/core/Vertx.java @@ -355,6 +355,24 @@ default DatagramSocket createDatagramSocket() { */ DnsClient createDnsClient(); + /** + * Create a DoH client to connect to a DoH server at the specified host, with the default query timeout (5 seconds) + *

+ * + * @param host the host + * @return the DNS client + */ + DnsClient createDohClient(String host); + + /** + * Create a DoH client to connect to the DoH server configured by {@link VertxOptions#getAddressResolverOptions()} + *

+ * DNS client takes the first configured resolver address provided by {@link DnsResolverProvider#nameServerAddresses()}} + * + * @return the DNS client + */ + DnsClient createDohClient(); + /** * Create a DNS client to connect to a DNS server * diff --git a/src/main/java/io/vertx/core/dns/DnsClientOptions.java b/src/main/java/io/vertx/core/dns/DnsClientOptions.java index 45e2b3b6e0d..e2b20b3e39d 100644 --- a/src/main/java/io/vertx/core/dns/DnsClientOptions.java +++ b/src/main/java/io/vertx/core/dns/DnsClientOptions.java @@ -36,6 +36,11 @@ public class DnsClientOptions { */ public static final String DEFAULT_HOST = null; + /** + * The default value for the SSL = {@code false} (configured by {@link VertxOptions#getAddressResolverOptions()}) + */ + public static final boolean DEFAULT_SSL = false; + /** * The default value for the query timeout in millis = {@code 5000} */ @@ -58,6 +63,7 @@ public class DnsClientOptions { private int port = DEFAULT_PORT; private String host = DEFAULT_HOST; + private boolean ssl = DEFAULT_SSL; private long queryTimeout = DEFAULT_QUERY_TIMEOUT; private boolean logActivity = DEFAULT_LOG_ENABLED; private ByteBufFormat activityLogFormat = DEFAULT_LOG_ACTIVITY_FORMAT; @@ -77,6 +83,7 @@ public DnsClientOptions(DnsClientOptions other) { logActivity = other.logActivity; activityLogFormat = other.activityLogFormat; recursionDesired = other.recursionDesired; + ssl = other.ssl; } /** @@ -197,6 +204,25 @@ public DnsClientOptions setRecursionDesired(boolean recursionDesired) { return this; } + /** + * Get the ssl to be used by this client in requests. + * + * @return the ssl + */ + public boolean isSsl() { + return ssl; + } + + /** + * Set the ssl to be used by this client in requests. + * + * @return a reference to this, so the API can be used fluently + */ + public DnsClientOptions setSsl(boolean ssl) { + this.ssl = ssl; + return this; + } + public JsonObject toJson() { JsonObject json = new JsonObject(); DnsClientOptionsConverter.toJson(this, json); diff --git a/src/main/java/io/vertx/core/dns/impl/BaseDnsClientImpl.java b/src/main/java/io/vertx/core/dns/impl/BaseDnsClientImpl.java new file mode 100644 index 00000000000..917ee5253e9 --- /dev/null +++ b/src/main/java/io/vertx/core/dns/impl/BaseDnsClientImpl.java @@ -0,0 +1,198 @@ +package io.vertx.core.dns.impl; + +import io.netty.handler.codec.dns.DnsRecordType; +import io.vertx.codegen.annotations.Nullable; +import io.vertx.core.AsyncResult; +import io.vertx.core.Future; +import io.vertx.core.Handler; +import io.vertx.core.dns.DnsClient; +import io.vertx.core.dns.DnsClientOptions; +import io.vertx.core.dns.MxRecord; +import io.vertx.core.dns.SrvRecord; +import io.vertx.core.impl.ContextInternal; +import io.vertx.core.impl.VertxInternal; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; + +/** + * @author imz + */ + + +public abstract class BaseDnsClientImpl implements DnsClient { + private static final char[] HEX_TABLE = "0123456789abcdef".toCharArray(); + protected DnsClientOptions options; + protected VertxInternal vertx; + protected ContextInternal context; + protected volatile Future closed; + + protected abstract Future> lookupList(String name, DnsRecordType... types); + + @Override + public DnsClient lookup4(String name, Handler> handler) { + lookup4(name).onComplete(handler); + return this; + } + + @Override + public Future<@Nullable String> lookup4(String name) { + return lookupSingle(name, DnsRecordType.A); + } + + @Override + public DnsClient lookup6(String name, Handler> handler) { + lookup6(name).onComplete(handler); + return this; + } + + @Override + public Future<@Nullable String> lookup6(String name) { + return lookupSingle(name, DnsRecordType.AAAA); + } + + @Override + public DnsClient lookup(String name, Handler> handler) { + lookup(name).onComplete(handler); + return this; + } + + @Override + public Future<@Nullable String> lookup(String name) { + return lookupSingle(name, DnsRecordType.A, DnsRecordType.AAAA); + } + + @Override + public DnsClient resolveA(String name, Handler>> handler) { + resolveA(name).onComplete(handler); + return this; + } + + @Override + public Future> resolveA(String name) { + return lookupList(name, DnsRecordType.A); + } + + @Override + public DnsClient resolveCNAME(String name, Handler >> handler) { + resolveCNAME(name).onComplete(handler); + return this; + } + + @Override + public Future> resolveCNAME(String name) { + return lookupList(name, DnsRecordType.CNAME); + } + + @Override + public DnsClient resolveMX(String name, Handler>> handler) { + resolveMX(name).onComplete(handler); + return this; + } + + @Override + public Future> resolveMX(String name) { + return lookupList(name, DnsRecordType.MX); + } + + @Override + public DnsClient resolveTXT(String name, Handler>> handler) { + resolveTXT(name).onComplete(handler); + return this; + } + + @Override + public Future<@Nullable String> resolvePTR(String name) { + return lookupSingle(name, DnsRecordType.PTR); + } + + @Override + public DnsClient resolvePTR(String name, Handler> handler) { + resolvePTR(name).onComplete(handler); + return this; + } + + @Override + public DnsClient resolveAAAA(String name, Handler>> handler) { + resolveAAAA(name).onComplete(handler); + return this; + } + + @Override + public Future> resolveAAAA(String name) { + return lookupList(name, DnsRecordType.AAAA); + } + + @Override + public Future> resolveNS(String name) { + return lookupList(name, DnsRecordType.NS); + } + + @Override + public DnsClient resolveNS(String name, Handler>> handler) { + resolveNS(name).onComplete(handler); + return this; + } + + @Override + public Future> resolveSRV(String name) { + return lookupList(name, DnsRecordType.SRV); + } + + @Override + public DnsClient resolveSRV(String name, Handler>> handler) { + resolveSRV(name).onComplete(handler); + return this; + } + + @Override + public Future<@Nullable String> reverseLookup(String address) { + try { + InetAddress inetAddress = InetAddress.getByName(address); + byte[] addr = inetAddress.getAddress(); + + StringBuilder reverseName = new StringBuilder(64); + if (inetAddress instanceof Inet4Address) { + // reverse ipv4 address + reverseName.append(addr[3] & 0xff).append(".") + .append(addr[2]& 0xff).append(".") + .append(addr[1]& 0xff).append(".") + .append(addr[0]& 0xff); + } else { + // It is an ipv 6 address time to reverse it + for (int i = 0; i < 16; i++) { + reverseName.append(HEX_TABLE[(addr[15 - i] & 0xf)]); + reverseName.append("."); + reverseName.append(HEX_TABLE[(addr[15 - i] >> 4) & 0xf]); + if (i != 15) { + reverseName.append("."); + } + } + } + reverseName.append(".in-addr.arpa"); + + return resolvePTR(reverseName.toString()); + } catch (UnknownHostException e) { + // Should never happen as we work with ip addresses as input + // anyway just in case notify the handler + return Future.failedFuture(e); + } + } + + @Override + public DnsClient reverseLookup(String address, Handler> handler) { + reverseLookup(address).onComplete(handler); + return this; + } + + private Future lookupSingle(String name, DnsRecordType... types) { + return this.lookupList(name, types).map(result -> result.isEmpty() ? null : result.get(0)); + } + + @Override + public void close(Handler> handler) { + close().onComplete(handler); + } +} diff --git a/src/main/java/io/vertx/core/dns/impl/DnsClientImpl.java b/src/main/java/io/vertx/core/dns/impl/DnsClientImpl.java index 4af46d05a4d..7947f65f905 100644 --- a/src/main/java/io/vertx/core/dns/impl/DnsClientImpl.java +++ b/src/main/java/io/vertx/core/dns/impl/DnsClientImpl.java @@ -19,22 +19,23 @@ import io.netty.handler.logging.LoggingHandler; import io.netty.util.collection.LongObjectHashMap; import io.netty.util.collection.LongObjectMap; -import io.vertx.codegen.annotations.Nullable; -import io.vertx.core.*; -import io.vertx.core.dns.*; +import io.vertx.core.Future; +import io.vertx.core.Handler; +import io.vertx.core.VertxException; +import io.vertx.core.buffer.impl.PartialPooledByteBufAllocator; +import io.vertx.core.dns.DnsClientOptions; +import io.vertx.core.dns.DnsException; import io.vertx.core.dns.DnsResponseCode; +import io.vertx.core.dns.impl.decoder.JsonHelper; import io.vertx.core.dns.impl.decoder.RecordDecoder; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.impl.VertxInternal; -import io.vertx.core.buffer.impl.PartialPooledByteBufAllocator; +import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.net.impl.ConnectionBase; import io.vertx.core.spi.transport.Transport; import java.net.Inet4Address; -import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -44,17 +45,11 @@ /** * @author Norman Maurer */ -public final class DnsClientImpl implements DnsClient { +public final class DnsClientImpl extends BaseDnsClientImpl { - private static final char[] HEX_TABLE = "0123456789abcdef".toCharArray(); - - private final VertxInternal vertx; - private final LongObjectMap inProgressMap = new LongObjectHashMap<>(); + private final LongObjectMap inProgressMap = new LongObjectHashMap<>(); private final InetSocketAddress dnsServer; - private final ContextInternal context; private final DatagramChannel channel; - private final DnsClientOptions options; - private volatile Future closed; public DnsClientImpl(VertxInternal vertx, DnsClientOptions options) { Objects.requireNonNull(options, "no null options accepted"); @@ -70,7 +65,8 @@ public DnsClientImpl(VertxInternal vertx, DnsClientOptions options) { Transport transport = vertx.transport(); context = vertx.getOrCreateContext(); - channel = transport.datagramChannel(this.dnsServer.getAddress() instanceof Inet4Address ? InternetProtocolFamily.IPv4 : InternetProtocolFamily.IPv6); + channel = transport.datagramChannel(this.dnsServer.getAddress() instanceof Inet4Address ? + InternetProtocolFamily.IPv4 : InternetProtocolFamily.IPv6); channel.config().setOption(ChannelOption.DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION, true); MaxMessagesRecvByteBufAllocator bufAllocator = channel.config().getRecvByteBufAllocator(); bufAllocator.maxMessagesPerRead(1); @@ -93,179 +89,9 @@ protected void channelRead0(ChannelHandlerContext ctx, DnsResponse msg) { }); } - @Override - public DnsClient lookup4(String name, Handler> handler) { - lookup4(name).onComplete(handler); - return this; - } - - @Override - public Future<@Nullable String> lookup4(String name) { - return lookupSingle(name, DnsRecordType.A); - } - - @Override - public DnsClient lookup6(String name, Handler> handler) { - lookup6(name).onComplete(handler); - return this; - } - - @Override - public Future<@Nullable String> lookup6(String name) { - return lookupSingle(name, DnsRecordType.AAAA); - } - - @Override - public DnsClient lookup(String name, Handler> handler) { - lookup(name).onComplete(handler); - return this; - } - - @Override - public Future<@Nullable String> lookup(String name) { - return lookupSingle(name, DnsRecordType.A, DnsRecordType.AAAA); - } - - @Override - public DnsClient resolveA(String name, Handler>> handler) { - resolveA(name).onComplete(handler); - return this; - } - - @Override - public Future> resolveA(String name) { - return lookupList(name, DnsRecordType.A); - } - - @Override - public DnsClient resolveCNAME(String name, Handler >> handler) { - resolveCNAME(name).onComplete(handler); - return this; - } - - @Override - public Future> resolveCNAME(String name) { - return lookupList(name, DnsRecordType.CNAME); - } - - @Override - public DnsClient resolveMX(String name, Handler>> handler) { - resolveMX(name).onComplete(handler); - return this; - } - - @Override - public Future> resolveMX(String name) { - return lookupList(name, DnsRecordType.MX); - } - - @Override - public Future> resolveTXT(String name) { - return this.>lookupList(name, DnsRecordType.TXT).map(records -> { - List txts = new ArrayList<>(); - for (List txt: records) { - txts.addAll(txt); - } - return txts; - }); - } - - @Override - public DnsClient resolveTXT(String name, Handler>> handler) { - resolveTXT(name).onComplete(handler); - return this; - } - - @Override - public Future<@Nullable String> resolvePTR(String name) { - return lookupSingle(name, DnsRecordType.PTR); - } - - @Override - public DnsClient resolvePTR(String name, Handler> handler) { - resolvePTR(name).onComplete(handler); - return this; - } - - @Override - public DnsClient resolveAAAA(String name, Handler>> handler) { - resolveAAAA(name).onComplete(handler); - return this; - } - - @Override - public Future> resolveAAAA(String name) { - return lookupList(name, DnsRecordType.AAAA); - } - - @Override - public Future> resolveNS(String name) { - return lookupList(name, DnsRecordType.NS); - } - - @Override - public DnsClient resolveNS(String name, Handler>> handler) { - resolveNS(name).onComplete(handler); - return this; - } - - @Override - public Future> resolveSRV(String name) { - return lookupList(name, DnsRecordType.SRV); - } - - @Override - public DnsClient resolveSRV(String name, Handler>> handler) { - resolveSRV(name).onComplete(handler); - return this; - } - - @Override - public Future<@Nullable String> reverseLookup(String address) { - try { - InetAddress inetAddress = InetAddress.getByName(address); - byte[] addr = inetAddress.getAddress(); - - StringBuilder reverseName = new StringBuilder(64); - if (inetAddress instanceof Inet4Address) { - // reverse ipv4 address - reverseName.append(addr[3] & 0xff).append(".") - .append(addr[2]& 0xff).append(".") - .append(addr[1]& 0xff).append(".") - .append(addr[0]& 0xff); - } else { - // It is an ipv 6 address time to reverse it - for (int i = 0; i < 16; i++) { - reverseName.append(HEX_TABLE[(addr[15 - i] & 0xf)]); - reverseName.append("."); - reverseName.append(HEX_TABLE[(addr[15 - i] >> 4) & 0xf]); - if (i != 15) { - reverseName.append("."); - } - } - } - reverseName.append(".in-addr.arpa"); - - return resolvePTR(reverseName.toString()); - } catch (UnknownHostException e) { - // Should never happen as we work with ip addresses as input - // anyway just in case notify the handler - return Future.failedFuture(e); - } - } - - @Override - public DnsClient reverseLookup(String address, Handler> handler) { - reverseLookup(address).onComplete(handler); - return this; - } - - private Future lookupSingle(String name, DnsRecordType... types) { - return this.lookupList(name, types).map(result -> result.isEmpty() ? null : result.get(0)); - } @SuppressWarnings("unchecked") - private Future> lookupList(String name, DnsRecordType... types) { + protected Future> lookupList(String name, DnsRecordType... types) { ContextInternal ctx = vertx.getOrCreateContext(); if (closed != null) { return ctx.failedFuture(ConnectionBase.CLOSED_EXCEPTION); @@ -303,11 +129,10 @@ private class Query { long timerID; public Query(String name, DnsRecordType[] types) { - this.msg = new DatagramDnsQuery(null, dnsServer, ThreadLocalRandom.current().nextInt()).setRecursionDesired(options.isRecursionDesired()); - if (!name.endsWith(".")) { - name += "."; - } - for (DnsRecordType type: types) { + this.msg = + new DatagramDnsQuery(null, dnsServer, ThreadLocalRandom.current().nextInt()).setRecursionDesired(options.isRecursionDesired()); + name = JsonHelper.appendDotIfRequired(name); + for (DnsRecordType type : types) { msg.addRecord(DnsSection.QUESTION, new DefaultDnsQuestion(name, type, DnsRecord.CLASS_IN)); } this.promise = context.nettyEventLoop().newPromise(); @@ -332,7 +157,7 @@ void handle(DnsResponse msg) { } int count = msg.count(DnsSection.ANSWER); List records = new ArrayList<>(count); - for (int idx = 0;idx < count;idx++) { + for (int idx = 0; idx < count; idx++) { DnsRecord a = msg.recordAt(DnsSection.ANSWER, idx); T record; try { @@ -381,8 +206,14 @@ private boolean isRequestedType(DnsRecordType dnsRecordType, DnsRecordType[] typ } @Override - public void close(Handler> handler) { - close().onComplete(handler); + public Future> resolveTXT(String name) { + return this.>lookupList(name, DnsRecordType.TXT).map(records -> { + List txts = new ArrayList<>(); + for (List txt: records) { + txts.addAll(txt); + } + return txts; + }); } @Override diff --git a/src/main/java/io/vertx/core/dns/impl/DohClientImpl.java b/src/main/java/io/vertx/core/dns/impl/DohClientImpl.java new file mode 100644 index 00000000000..57cf9a6aabf --- /dev/null +++ b/src/main/java/io/vertx/core/dns/impl/DohClientImpl.java @@ -0,0 +1,209 @@ +package io.vertx.core.dns.impl; + +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.dns.DnsRecordType; +import io.vertx.core.*; +import io.vertx.core.dns.DnsClientOptions; +import io.vertx.core.dns.DnsException; +import io.vertx.core.dns.DnsResponseCode; +import io.vertx.core.dns.dnsrecord.DohRecord; +import io.vertx.core.dns.dnsrecord.DohResourceRecord; +import io.vertx.core.dns.impl.decoder.DohRecordDecoder; +import io.vertx.core.dns.impl.decoder.JsonHelper; +import io.vertx.core.http.*; +import io.vertx.core.impl.ContextInternal; +import io.vertx.core.impl.VertxInternal; +import io.vertx.core.json.JsonObject; +import io.vertx.core.net.impl.ConnectionBase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; + +import static io.vertx.core.dns.impl.decoder.JsonHelper.appendDotIfRequired; + +/** + * @author imz.987 + */ +public class DohClientImpl extends BaseDnsClientImpl { + public static final String DOH_URL_FORMAT = "/dns-query?name=%s&type=%s"; + private final HttpClient httpClient; + private final ConcurrentMap> inProgressMap = new ConcurrentHashMap<>(); + + public DohClientImpl(VertxInternal vertx, DnsClientOptions options) { + Objects.requireNonNull(options, "no null options accepted"); + Objects.requireNonNull(options.getHost(), "no null host accepted"); + + this.options = new DnsClientOptions(options); + + this.vertx = vertx; + this.context = vertx.getOrCreateContext(); + + HttpClientOptions httpClientOptions = new HttpClientOptions() + .setLogActivity(options.getLogActivity()) + .setActivityLogDataFormat(options.getActivityLogFormat()) + .setSsl(true) + .setUseAlpn(true) + .setTrustAll(true) + .setVerifyHost(false) + .setProtocolVersion(HttpVersion.HTTP_2); + + this.httpClient = vertx.createHttpClient(httpClientOptions); + } + + protected Future> lookupList(String name, DnsRecordType... types) { + ContextInternal ctx = vertx.getOrCreateContext(); + if (closed != null) { + return ctx.failedFuture(ConnectionBase.CLOSED_EXCEPTION); + } + Objects.requireNonNull(name, "no null name accepted"); + + Query query = new Query<>(name, types); + return query.run(); + } + + private class Query { + final Promise> promise; + final String name; + final DnsRecordType[] types; + long timerID; + int id; + + public Query(String name, DnsRecordType[] types) { + this.types = types; + this.promise = Promise.promise(); + this.name = appendDotIfRequired(name); + this.id = ThreadLocalRandom.current().nextInt(); + } + + public Future> run() { + inProgressMap.put(id, this); + timerID = vertx.setTimer(options.getQueryTimeout(), id -> { + timerID = -1; + context.runOnContext(v -> fail(new VertxException("DNS query timeout for " + name))); + }); + + List>> futures = Arrays.stream(types).map(this::fetchDns).collect(Collectors.toList()); + + Future.all(futures) + .onSuccess(compositeFuture -> promise.tryComplete(combineCompositeFutureResults(compositeFuture))) + .onFailure(t -> context.emit(t, this::fail)); + + return promise.future(); + } + + private Future> fetchDns(DnsRecordType type) { + RequestOptions requestOptions = new RequestOptions() + .setSsl(true) + .setPort(options.getPort()) + .setHost(options.getHost()) + .setURI(String.format(DOH_URL_FORMAT, name, type.name())) + .setMethod(HttpMethod.GET) + .addHeader("accept", "application/dns-json"); + + return httpClient.request(requestOptions) + .compose(HttpClientRequest::send) + .compose(HttpClientResponse::body) + .map(buf -> new DohRecord(JsonHelper.normalizePropertyNames(new JsonObject(buf)))) + .compose(this::handleResolvedDns); + } + + private List combineCompositeFutureResults(CompositeFuture compositeFuture) { + List result = new ArrayList<>(); + for (int i = 0; i < compositeFuture.size(); i++) { + if (compositeFuture.resultAt(i) instanceof List) { + result.addAll(compositeFuture.>resultAt(i)); + } else { + result.add(compositeFuture.resultAt(i)); + } + } + return result; + } + + private Future> handleResolvedDns(DohRecord resolvedDns) { + DnsResponseCode code = DnsResponseCode.valueOf(resolvedDns.getStatus()); + + inProgressMap.remove(id); + if (timerID >= 0) { + vertx.cancelTimer(timerID); + } + + if (code != DnsResponseCode.NOERROR) { + fail(new DnsException(code)); + return promise.future(); + } + + List result = new ArrayList<>(); + + if (resolvedDns.getAnswer() != null) { + for (DohResourceRecord answer : resolvedDns.getAnswer()) { + T decoded = decodeResourceRecord(answer); + if (decoded == null) { + return promise.future(); + } + if (isRequestedType(DnsRecordType.valueOf(answer.getType()), types)) { + result.add(decoded); + } + } + } + if (resolvedDns.getAuthority() != null) { + for (DohResourceRecord authority : resolvedDns.getAuthority()) { + result.add(DohRecordDecoder.decode(authority)); + } + } + + return Future.succeededFuture(result); + } + + private T decodeResourceRecord(DohResourceRecord resourceRecord) { + try { + return DohRecordDecoder.decode(resourceRecord); + } catch (DecoderException e) { + fail(e); + } + return null; + } + + private void fail(Throwable cause) { + inProgressMap.remove(id); + if (timerID >= 0) { + vertx.cancelTimer(timerID); + } + promise.tryFail(cause); + } + + private boolean isRequestedType(DnsRecordType dnsRecordType, DnsRecordType[] types) { + List dnsRecordTypes = Arrays.asList(types); +// if(dnsRecordTypes.contains(DnsRecordType.SRV) && dnsRecordType == DnsRecordType.SOA) { +// return true; +// } + return dnsRecordTypes.contains(dnsRecordType); + } + } + + public void inProgressQueries(Handler handler) { + context.runOnContext(v -> handler.handle(inProgressMap.size())); + } + + @Override + public Future> resolveTXT(String name) { + return this.lookupList(name, DnsRecordType.TXT); + } + + @Override + public Future close() { + synchronized (this) { + if (closed != null) { + return closed; + } + closed = Future.succeededFuture(); + } + inProgressMap.values().forEach(query -> query.fail(ConnectionBase.CLOSED_EXCEPTION)); + return closed; + } +} diff --git a/src/main/java/io/vertx/core/dns/impl/decoder/DohRecordDecoder.java b/src/main/java/io/vertx/core/dns/impl/decoder/DohRecordDecoder.java new file mode 100644 index 00000000000..fc639469c36 --- /dev/null +++ b/src/main/java/io/vertx/core/dns/impl/decoder/DohRecordDecoder.java @@ -0,0 +1,107 @@ +package io.vertx.core.dns.impl.decoder; + +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.dns.DnsRecordType; +import io.vertx.core.dns.dnsrecord.DohResourceRecord; +import io.vertx.core.dns.impl.MxRecordImpl; +import io.vertx.core.dns.impl.SrvRecordImpl; +import io.vertx.core.impl.logging.Logger; +import io.vertx.core.impl.logging.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * @author imz.987 + */ +public class DohRecordDecoder { + private static final Logger log = LoggerFactory.getLogger(DohRecordDecoder.class); + + + /** + * Decodes MX (mail exchanger) resource records. + */ + public static final Function MX = dohResourceRecord -> { + String[] parts = dohResourceRecord.getData().replaceAll("[\\[\\]]", "").split(" ", 2); + int priority = Integer.parseInt(parts[0]); + String name = parts[1]; + return new MxRecordImpl(priority, name); + }; + + /** + * Decodes SOA (start of authority) resource records. + */ + public static final Function SOA = dohResourceRecord -> { + String[] parts = dohResourceRecord.getData().split(" "); + String mName = parts[0]; + String rName = parts[1]; + long serial = Long.parseLong(parts[2]); + int refresh = Integer.parseInt(parts[3]); + int retry = Integer.parseInt(parts[4]); + int expire = Integer.parseInt(parts[5]); + long minimum = Long.parseLong(parts[6]); + + return new StartOfAuthorityRecord(mName, rName, serial, refresh, retry, expire, minimum); + }; + + + /** + * Decodes SRV (service) resource records. + */ + public static final Function SRV = dohResourceRecord -> { + String[] parts = dohResourceRecord.getData().split(" "); + int priority = Integer.parseInt(parts[0]); + int weight = Integer.parseInt(parts[1]); + int port = Integer.parseInt(parts[2]); + String name = parts[3]; + String protocol = parts[4]; + String service = parts[5]; + String target = parts[6]; + + return new SrvRecordImpl(priority, weight, port, name, protocol, service, target); + }; + + /** + * Decodes default resource records to extract and return the data property. + */ + public static final Function DEFAULT = DohResourceRecord::getData; + private static final Map> decoders = new HashMap<>(); + + static { + decoders.put(DnsRecordType.MX, DohRecordDecoder.MX); + decoders.put(DnsRecordType.SRV, DohRecordDecoder.SRV); + decoders.put(DnsRecordType.SOA, DohRecordDecoder.SOA); + + decoders.put(DnsRecordType.A, DohRecordDecoder.DEFAULT); + decoders.put(DnsRecordType.AAAA, DohRecordDecoder.DEFAULT); + decoders.put(DnsRecordType.TXT, DohRecordDecoder.DEFAULT); + decoders.put(DnsRecordType.NS, DohRecordDecoder.DEFAULT); + decoders.put(DnsRecordType.CNAME, DohRecordDecoder.DEFAULT); + decoders.put(DnsRecordType.PTR, DohRecordDecoder.DEFAULT); + } + + /** + * Decodes a resource record and returns the result. + * + * @param record + * @return the decoded resource record + */ + + @SuppressWarnings("unchecked") + public static T decode(DohResourceRecord record) { + DnsRecordType type = DnsRecordType.valueOf(record.getType()); + Function decoder = decoders.get(type); + if (decoder == null) { + throw new DecoderException("DNS record decoding error occurred: Unsupported resource record type [id: " + type + "]."); + } + T result = null; + try { + result = (T) decoder.apply(record); + } catch (Exception e) { + log.error(e.getMessage(), e.getCause()); + } + return result; + } + +} diff --git a/src/main/java/io/vertx/core/dns/impl/decoder/JsonHelper.java b/src/main/java/io/vertx/core/dns/impl/decoder/JsonHelper.java new file mode 100644 index 00000000000..1056c44067f --- /dev/null +++ b/src/main/java/io/vertx/core/dns/impl/decoder/JsonHelper.java @@ -0,0 +1,37 @@ +package io.vertx.core.dns.impl.decoder; + +import io.vertx.codegen.Helper; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +public class JsonHelper { + public static T normalizePropertyNames(T obj) { + if (obj instanceof JsonObject) { + JsonObject json = new JsonObject(); + ((JsonObject) obj).forEach(member -> json.put(normalizePropertyName(member.getKey()), normalizePropertyNames(member.getValue()))); + return (T) json; + } + + if (obj instanceof JsonArray) { + JsonArray json = new JsonArray(); + ((JsonArray) obj).forEach(item -> json.add(normalizePropertyNames(item))); + return (T) json; + } + + return obj; + } + + private static String normalizePropertyName(String text) { + if(text == null || text.isEmpty()) + return text; + return Helper.normalizePropertyName(text); + } + + public static String appendDotIfRequired(String name) { + if (!name.endsWith(".")) { + name += "."; + } + return name; + } + +} diff --git a/src/main/java/io/vertx/core/dns/impl/decoder/StartOfAuthorityRecord.java b/src/main/java/io/vertx/core/dns/impl/decoder/StartOfAuthorityRecord.java index 5b8c2c367e5..9da9321102d 100644 --- a/src/main/java/io/vertx/core/dns/impl/decoder/StartOfAuthorityRecord.java +++ b/src/main/java/io/vertx/core/dns/impl/decoder/StartOfAuthorityRecord.java @@ -116,4 +116,16 @@ public long minimumTtl() { return minimumTtl; } + @Override + public String toString() { + return String.join(" ", + primaryNameServer(), + responsiblePerson(), + String.valueOf(serial()), + String.valueOf(refreshTime()), + String.valueOf(retryTime()), + String.valueOf(expireTime()), + String.valueOf(minimumTtl()) + ); + } } diff --git a/src/main/java/io/vertx/core/impl/VertxImpl.java b/src/main/java/io/vertx/core/impl/VertxImpl.java index e17ba95c51b..6b1cca594e1 100644 --- a/src/main/java/io/vertx/core/impl/VertxImpl.java +++ b/src/main/java/io/vertx/core/impl/VertxImpl.java @@ -17,8 +17,8 @@ import io.netty.util.ResourceLeakDetector; import io.netty.util.concurrent.GenericFutureListener; import io.vertx.core.Future; -import io.vertx.core.*; import io.vertx.core.Timer; +import io.vertx.core.*; import io.vertx.core.datagram.DatagramSocket; import io.vertx.core.datagram.DatagramSocketOptions; import io.vertx.core.datagram.impl.DatagramSocketImpl; @@ -26,33 +26,30 @@ import io.vertx.core.dns.DnsClient; import io.vertx.core.dns.DnsClientOptions; import io.vertx.core.dns.impl.DnsClientImpl; +import io.vertx.core.dns.impl.DohClientImpl; import io.vertx.core.eventbus.EventBus; import io.vertx.core.eventbus.impl.EventBusImpl; import io.vertx.core.eventbus.impl.EventBusInternal; import io.vertx.core.eventbus.impl.clustered.ClusteredEventBus; import io.vertx.core.file.FileSystem; +import io.vertx.core.file.impl.FileSystemImpl; +import io.vertx.core.file.impl.WindowsFileSystem; import io.vertx.core.http.*; import io.vertx.core.http.impl.*; import io.vertx.core.impl.btc.BlockedThreadChecker; -import io.vertx.core.net.impl.NetClientBuilder; -import io.vertx.core.impl.transports.JDKTransport; -import io.vertx.core.spi.file.FileResolver; -import io.vertx.core.file.impl.FileSystemImpl; -import io.vertx.core.file.impl.WindowsFileSystem; -import io.vertx.core.http.impl.HttpClientImpl; -import io.vertx.core.http.impl.HttpServerImpl; import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; import io.vertx.core.impl.resolver.DnsResolverProvider; +import io.vertx.core.impl.transports.JDKTransport; import io.vertx.core.net.NetClient; import io.vertx.core.net.NetClientOptions; import io.vertx.core.net.NetServer; import io.vertx.core.net.NetServerOptions; +import io.vertx.core.net.impl.NetClientBuilder; import io.vertx.core.net.impl.NetServerImpl; import io.vertx.core.net.impl.ServerID; import io.vertx.core.net.impl.TCPServerBase; -import io.vertx.core.spi.transport.Transport; import io.vertx.core.shareddata.SharedData; import io.vertx.core.shareddata.impl.SharedDataImpl; import io.vertx.core.spi.ExecutorServiceFactory; @@ -60,11 +57,13 @@ import io.vertx.core.spi.VertxThreadFactory; import io.vertx.core.spi.cluster.ClusterManager; import io.vertx.core.spi.cluster.NodeSelector; +import io.vertx.core.spi.file.FileResolver; import io.vertx.core.spi.metrics.Metrics; import io.vertx.core.spi.metrics.MetricsProvider; import io.vertx.core.spi.metrics.PoolMetrics; import io.vertx.core.spi.metrics.VertxMetrics; import io.vertx.core.spi.tracing.VertxTracer; +import io.vertx.core.spi.transport.Transport; import java.io.File; import java.io.IOException; @@ -96,6 +95,7 @@ public class VertxImpl implements VertxInternal, MetricsProvider { private static final String CLUSTER_MAP_NAME = "__vertx.haInfo"; private static final String NETTY_IO_RATIO_PROPERTY_NAME = "vertx.nettyIORatio"; private static final int NETTY_IO_RATIO = Integer.getInteger(NETTY_IO_RATIO_PROPERTY_NAME, 50); + public static final String DEFAULT_DOH_HOST = "1.1.1.1"; // Not cached for graalvm private static ThreadFactory virtualThreadFactory() { @@ -624,19 +624,35 @@ public DnsClient createDnsClient() { return createDnsClient(new DnsClientOptions()); } + @Override + public DnsClient createDohClient(String host) { + return createDnsClient(new DnsClientOptions().setSsl(true).setPort(443).setHost(host)); + } + + @Override + public DnsClient createDohClient() { + return createDnsClient(new DnsClientOptions().setSsl(true)); + } + @Override public DnsClient createDnsClient(DnsClientOptions options) { String host = options.getHost(); int port = options.getPort(); if (host == null || port < 0) { - DnsResolverProvider provider = DnsResolverProvider.create(this, addressResolverOptions); - InetSocketAddress address = provider.nameServerAddresses().get(0); - // provide the host and port - options = new DnsClientOptions(options) - .setHost(address.getAddress().getHostAddress()) - .setPort(address.getPort()); + if (options.isSsl()) { + options = new DnsClientOptions(options) + .setHost(DEFAULT_DOH_HOST) + .setPort(443); + } else { + DnsResolverProvider provider = DnsResolverProvider.create(this, addressResolverOptions); + InetSocketAddress address = provider.nameServerAddresses().get(0); + // provide the host and port + options = new DnsClientOptions(options) + .setHost(address.getAddress().getHostAddress()) + .setPort(address.getPort()); + } } - return new DnsClientImpl(this, options); + return options.isSsl() ? new DohClientImpl(this, options) : new DnsClientImpl(this, options); } private long scheduleTimeout(ContextInternal context, diff --git a/src/main/java/io/vertx/core/impl/VertxWrapper.java b/src/main/java/io/vertx/core/impl/VertxWrapper.java index 54a17a0dbf2..71c7d159e68 100644 --- a/src/main/java/io/vertx/core/impl/VertxWrapper.java +++ b/src/main/java/io/vertx/core/impl/VertxWrapper.java @@ -44,10 +44,8 @@ import java.net.InetSocketAddress; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; -import java.util.function.Function; import java.util.function.Supplier; /** @@ -115,6 +113,16 @@ public DnsClient createDnsClient() { return delegate.createDnsClient(); } + @Override + public DnsClient createDohClient(String host) { + return delegate.createDohClient(host); + } + + @Override + public DnsClient createDohClient() { + return delegate.createDohClient(); + } + @Override public DnsClient createDnsClient(DnsClientOptions options) { return delegate.createDnsClient(options); diff --git a/src/test/java/io/vertx/test/tls/Trust.java b/src/test/java/io/vertx/test/tls/Trust.java index c10d59caca1..fb0ff863ef9 100644 --- a/src/test/java/io/vertx/test/tls/Trust.java +++ b/src/test/java/io/vertx/test/tls/Trust.java @@ -39,6 +39,7 @@ public interface Trust extends Supplier { Trust SNI_JKS_HOST3 = () -> new JksOptions().setPath("tls/sni-truststore-host3.jks").setPassword("wibble"); Trust SNI_JKS_HOST4 = () -> new JksOptions().setPath("tls/sni-truststore-host4.jks").setPassword("wibble"); Trust SNI_JKS_HOST5 = () -> new JksOptions().setPath("tls/sni-truststore-host5.jks").setPassword("wibble"); + Trust DOH_JKS_HOST = () -> new JksOptions().setPath("tls/server-keystore-doh.jks").setPassword("wibble"); Trust SNI_SERVER_ROOT_CA_AND_OTHER_CA_1 = () -> new JksOptions().setPath("tls/server-truststore-root-ca-host2.jks").setPassword("wibble"); Trust SNI_SERVER_ROOT_CA_AND_OTHER_CA_2 = () -> new JksOptions().setPath("tls/server-truststore-root-ca-host3.jks").setPassword("wibble"); Trust SNI_SERVER_ROOT_CA_FALLBACK = () -> new JksOptions().setPath("tls/server-truststore-root-ca-fallback.jks").setPassword("wibble"); diff --git a/src/test/resources/tls/server-keystore-doh.jks b/src/test/resources/tls/server-keystore-doh.jks new file mode 100644 index 0000000000000000000000000000000000000000..dd4212de469b88b36b2a9c222e98163dcf9264ea GIT binary patch literal 2702 zcma)8X*d*&7M?Ltf9 z$Ud@u*2LJ$zLV=d_db31{<`4-^mr;(t70jF{YZT`1 z5rUEg^ZDh7iiBm*l}$(3kVnLm14w)9CKHo54-*iMg0Qgt%aMtl8Gt&+!fqF40K5wV z1Cd}Jtu9)=tm?EdfWOM)o`#JYm;|Qdg({Lhc}kgg*0BR!4czc%hFU=x!qvtVT&vnE z9y7T9NkU-`O@0rh``T%gOPS|U;UgQB^?ohcrHNT&P zu^)F&Qf)`q5{laR5vXdDslFFCxOYDd2{W!|vV<6?r0ha9<2j?S(=tUd36>T`usS4{t@I1- z?&qMT+;>-%ZMzhWb56lu8|f^pYS>?x>y0s@Ev0eG-;acurQZNEYJS4Dxv3@2u7}qI1F%UUk6-U>qLVHPMBRBJ zD;({Z5x*ExlYB6j;DoK%;OSn5j)@KORPlJ8w7M`e(`jQn=C`obt*_E)R@Vc{;|})f z$=3=M4Al^0TQ&?VFbyh6yf(NxRQlda)Sny+#fV40%%-qul}ZG#G^84QuKYfwV*LQMo8v+KGAqr6%9|8+a zj&Rrowu3nz4 zgQY>23Gch;`Y?;li6J6XQQeQmDmrL+tBd!ol@X6$kAQ>d_U94p3uQ-UZ|BGT-nvb6 z75jIm7Dz@T2}F^pePw?GV6kA*7Rw!X0x>ereG^fMUEftd&u=rX2z;DanRGCW zt+K-vmP?yCOjfX){E^|}xQA|jS|A>j8koL_Ek*+tZHifD|EvbYsUBL6uT&@0FVXZ0 zrf&LKYi_Z2A82e$#5BzKgQPT_Gd4cpEG8i<6AWI@dV}=A+c`LV^PFH)dDMEUYae)8 z^g@Hv0Ccd#D9Gjdclec3ySGfeP3XFs2h$G7Mp?_1UiG2jOJ!hf>-_W2;S@8bl*U-2 za8oy4shXeuH%-ky4}g+dB{XLR3@h{`ve6iuWD6Hw`EWhoRjV}>9uN66%guUNVjyHx z2mxqhLk1l!a}<{w@KxRzRNdYZ=_19kuXSQ?=X=PxlqSi5Vh&jA zy`Y?ev&%)#41o#^?B2$R=3NV!9z43pAi1u_XkRdhD`@{B-CIBzm*FXcQ>IKnz;V<6djtGmVJ(+R@h>@aiC!_BSv6vYHT8(FCjCEQ z{V@1YK*MAfH9vIuR}!&zI+z5a&t?eRo{H}OdLJuoVz$aAXWU(xoL-XjOfCSvbOw~> zx~Ok|Nu6)`la|RkXa^F?jD9!NQKk^!+uUygs2{EN3Novs!}TK1&&dhTyNqcw7_%EM zV|u*WihTbZLWG|%&p9(+Q@pn|Wb{%v+$SrQA7WPyJ)`K?rSZ|t$ykR2l?Ky9G)q;_ z(nnsE=$4hJP1C@5onEo+IxbGIDA}`yBA{JNP1Q8dFKHGs82i}PM};Dzclei zSE0`~K&X|P^@H&^I{(^OvLi;y!q-72Q>f7!KRnWB6JFL{dt_*;wqxosw1&}BTGK$p zhkwt@MW^7G8iixX=$4O=vp-cVg_xc46irtcBzLe;F=Xj0#fY_0?KA^0m%Gh#c5*}R*opleE z1{YaND$LUrTRv#^o!%B~_kvtR2!3cQ)Zs!}wtca2d-_O((_Q9|056*J=WKD+hzr0K z==7@tR_BGKDAKgZVm0Qaj|Qj2!m3#OqU7G_iR;;Z!{pqz;%Qo)Qh)U3r z5VzjX`Ox@N&QrUeee=ANdo%mE5W1$qaH=EW9(;!;Y!a^8{uC)y>bh5C~mk)e&RAG7wceN@yG zar?r>OAIEFiS)h@Q?Bs`Ma2)U)^iREy!CN3neOwdeHfp7bde@#m3CQ=&veB#=p$CpBqP6~5 z$N^`Yo3+)sJf0~G(LZeLe(-+HsE{u^I^<-umFVCV%FKi9_~EaiIINu(ML<12jXSSe zjhL#~)p28rz{;L&m7cS3t1)Zxy}FNte&YRr4%kE=-ABK!;snOLZa&wUaoavSsz2$J zJn|3Ji0%C+M#fADDeZ8^#Y} zk*_Kd^Rcp*Z5r%es~|q<;tqlgP5pdWUu~D5Vm|hzXdUhJYJ#w5nebfnosYp(u=#Br z`ip*7>vDFuHOd4fi{kwKqcH)Q0bsa$M{CMCR)EJKptdAG9ED|ZLo;lqU fo6-s9dw57rN5{K{k~h%CwXM&6P2dFFKO^NIq#@1M literal 0 HcmV?d00001 From 8e5605b1e1be484fb34f4535e0ed63aba503b07c Mon Sep 17 00:00:00 2001 From: imz87 Date: Sun, 23 Jun 2024 13:10:39 +0330 Subject: [PATCH 3/9] feat: add examples --- src/main/java/examples/DNSExamples.java | 279 +++++++++++++++++++++--- 1 file changed, 247 insertions(+), 32 deletions(-) diff --git a/src/main/java/examples/DNSExamples.java b/src/main/java/examples/DNSExamples.java index 570cd0e6bd7..2b34da2f617 100644 --- a/src/main/java/examples/DNSExamples.java +++ b/src/main/java/examples/DNSExamples.java @@ -40,7 +40,8 @@ public void example1__(Vertx vertx) { DnsClient client1 = vertx.createDnsClient(); // Just the same but with a different query timeout - DnsClient client2 = vertx.createDnsClient(new DnsClientOptions().setQueryTimeout(10000)); + DnsClient client2 = + vertx.createDnsClient(new DnsClientOptions().setQueryTimeout(10000)); } public void example2(Vertx vertx) { @@ -49,9 +50,9 @@ public void example2(Vertx vertx) { .lookup("vertx.io") .onComplete(ar -> { if (ar.succeeded()) { - System.out.println(ar.result()); + System.out.printf("Dns lookup result: %s\n", ar.result()); } else { - System.out.println("Failed to resolve entry" + ar.cause()); + System.out.println("Failed to dns lookup entry" + ar.cause()); } }); } @@ -62,9 +63,9 @@ public void example3(Vertx vertx) { .lookup4("vertx.io") .onComplete(ar -> { if (ar.succeeded()) { - System.out.println(ar.result()); + System.out.printf("Dns lookup4 result: %s\n", ar.result()); } else { - System.out.println("Failed to resolve entry" + ar.cause()); + System.out.println("Failed to dns lookup4 entry" + ar.cause()); } }); } @@ -75,9 +76,9 @@ public void example4(Vertx vertx) { .lookup6("vertx.io") .onComplete(ar -> { if (ar.succeeded()) { - System.out.println(ar.result()); + System.out.printf("Dns lookup6 result: %s\n", ar.result()); } else { - System.out.println("Failed to resolve entry" + ar.cause()); + System.out.println("Failed to dns lookup6 entry" + ar.cause()); } }); } @@ -89,11 +90,11 @@ public void example5(Vertx vertx) { .onComplete(ar -> { if (ar.succeeded()) { List records = ar.result(); - for (String record : records) { - System.out.println(record); + for (int i = 0; i < records.size(); i++) { + System.out.printf("Dns resolveA (%s): %s\n", i, records.get(i)); } } else { - System.out.println("Failed to resolve entry" + ar.cause()); + System.out.println("Failed to dns resolveA entry" + ar.cause()); } }); } @@ -105,11 +106,11 @@ public void example6(Vertx vertx) { .onComplete(ar -> { if (ar.succeeded()) { List records = ar.result(); - for (String record : records) { - System.out.println(record); + for (int i = 0; i < records.size(); i++) { + System.out.printf("Dns resolveAAAA (%s): %s\n", i, records.get(i)); } } else { - System.out.println("Failed to resolve entry" + ar.cause()); + System.out.println("Failed to dns resolveAAAA entry" + ar.cause()); } }); } @@ -121,11 +122,11 @@ public void example7(Vertx vertx) { .onComplete(ar -> { if (ar.succeeded()) { List records = ar.result(); - for (String record : records) { - System.out.println(record); + for (int i = 0; i < records.size(); i++) { + System.out.printf("Dns resolveCNAME (%s): %s\n", i, records.get(i)); } } else { - System.out.println("Failed to resolve entry" + ar.cause()); + System.out.println("Failed to dns resolveCNAME entry" + ar.cause()); } }); } @@ -137,11 +138,11 @@ public void example8(Vertx vertx) { .onComplete(ar -> { if (ar.succeeded()) { List records = ar.result(); - for (MxRecord record : records) { - System.out.println(record); + for (int i = 0; i < records.size(); i++) { + System.out.printf("Dns resolveMX (%s): %s\n", i, records.get(i)); } } else { - System.out.println("Failed to resolve entry" + ar.cause()); + System.out.println("Failed to dns resolveMX entry" + ar.cause()); } }); } @@ -158,11 +159,11 @@ public void example10(Vertx vertx) { .onComplete(ar -> { if (ar.succeeded()) { List records = ar.result(); - for (String record : records) { - System.out.println(record); + for (int i = 0; i < records.size(); i++) { + System.out.printf("Dns resolveTXT (%s): %s\n", i, records.get(i)); } } else { - System.out.println("Failed to resolve entry" + ar.cause()); + System.out.println("Failed to dns resolveTXT entry" + ar.cause()); } }); } @@ -174,11 +175,11 @@ public void example11(Vertx vertx) { .onComplete(ar -> { if (ar.succeeded()) { List records = ar.result(); - for (String record : records) { - System.out.println(record); + for (int i = 0; i < records.size(); i++) { + System.out.printf("Dns resolveNS (%s): %s\n", i, records.get(i)); } } else { - System.out.println("Failed to resolve entry" + ar.cause()); + System.out.println("Failed to dns resolveNS entry" + ar.cause()); } }); } @@ -190,11 +191,11 @@ public void example12(Vertx vertx) { .onComplete(ar -> { if (ar.succeeded()) { List records = ar.result(); - for (SrvRecord record : records) { - System.out.println(record); + for (int i = 0; i < records.size(); i++) { + System.out.printf("Dns resolveSRV (%s): %s\n", i, records.get(i)); } } else { - System.out.println("Failed to resolve entry" + ar.cause()); + System.out.println("Failed to dns resolveSRV entry" + ar.cause()); } }); } @@ -220,9 +221,9 @@ public void example14(Vertx vertx) { .onComplete(ar -> { if (ar.succeeded()) { String record = ar.result(); - System.out.println(record); + System.out.printf("Dns resolvePTR result: %s\n", record); } else { - System.out.println("Failed to resolve entry" + ar.cause()); + System.out.println("Failed to dns resolvePTR entry" + ar.cause()); } }); } @@ -234,10 +235,224 @@ public void example15(Vertx vertx) { .onComplete(ar -> { if (ar.succeeded()) { String record = ar.result(); - System.out.println(record); + System.out.printf("Dns reverseLookup result: %s\n", record); } else { - System.out.println("Failed to resolve entry" + ar.cause()); + System.out.println("Failed to dns reverseLookup entry" + ar.cause()); } }); } + + public void example16(Vertx vertx) { + DnsClient client = vertx.createDohClient(); + client + .lookup("vertx.io") + .onComplete(ar -> { + if (ar.succeeded()) { + System.out.printf("DoH lookup result: %s\n", ar.result()); + } else { + System.out.println("Failed to DoH lookup entry" + ar.cause()); + } + }); + } + + public void example17(Vertx vertx) { + DnsClient client = vertx.createDohClient(); + client + .lookup4("vertx.io") + .onComplete(ar -> { + if (ar.succeeded()) { + System.out.printf("DoH lookup4 result: %s\n", ar.result()); + } else { + System.out.println("Failed to DoH lookup4 entry" + ar.cause()); + } + }); + } + + public void example18(Vertx vertx) { + DnsClient client = vertx.createDohClient(); + client + .lookup6("google.com") + .onComplete(ar -> { + if (ar.succeeded()) { + System.out.printf("DoH lookup6 result: %s\n", ar.result()); + } else { + System.out.println("Failed to DoH lookup6 entry" + ar.cause()); + } + }); + } + public void example19(Vertx vertx) { + DnsClient client = vertx.createDohClient(); + client + .resolveA("vertx.io") + .onComplete(ar -> { + if (ar.succeeded()) { + List records = ar.result(); + for (int i = 0; i < records.size(); i++) { + System.out.printf("DoH resolveA (%s): %s\n", i, records.get(i));} + } else { + System.out.println("Failed to DoH resolveA entry" + ar.cause()); + } + }); + } + + public void example20(Vertx vertx) { + DnsClient client = vertx.createDohClient(); + client + .resolveAAAA("vertx.io") + .onComplete(ar -> { + if (ar.succeeded()) { + List records = ar.result(); + for (int i = 0; i < records.size(); i++) { + System.out.printf("DoH resolveAAAA (%s): %s\n", i, records.get(i)); + } + } else { + System.out.println("Failed to DoH resolveAAAA entry" + ar.cause()); + } + }); + } + + public void example21(Vertx vertx) { + DnsClient client = vertx.createDohClient(); + client + .resolveCNAME("vertx.io") + .onComplete(ar -> { + if (ar.succeeded()) { + List records = ar.result(); + for (int i = 0; i < records.size(); i++) { + System.out.printf("DoH resolveCNAME (%s): %s\n", i, records.get(i)); + } + } else { + System.out.println("Failed to DoH resolveCNAME entry" + ar.cause()); + } + }); + } + + public void example22(Vertx vertx) { + DnsClient client = vertx.createDohClient(); + client + .resolveMX("vertx.io") + .onComplete(ar -> { + if (ar.succeeded()) { + List records = ar.result(); + for (int i = 0; i < records.size(); i++) { + System.out.printf("DoH resolveMX (%s): %s\n", i, records.get(i)); + } + } else { + System.out.println("Failed to DoH resolveMX entry" + ar.cause()); + } + }); + } + + public void example23(Vertx vertx) { + DnsClient client = vertx.createDohClient(); + client + .resolveTXT("vertx.io") + .onComplete(ar -> { + if (ar.succeeded()) { + List records = ar.result(); + for (int i = 0; i < records.size(); i++) { + System.out.printf("DoH resolveTXT (%s): %s\n", i, records.get(i)); + } + } else { + System.out.println("Failed to DoH resolveTXT entry" + ar.cause()); + } + }); + } + + public void example24(Vertx vertx) { + DnsClient client = vertx.createDohClient(); + client + .resolveNS("vertx.io") + .onComplete(ar -> { + if (ar.succeeded()) { + List records = ar.result(); + for (int i = 0; i < records.size(); i++) { + System.out.printf("DoH resolveNS (%s): %s\n", i, records.get(i)); + } + } else { + System.out.println("Failed to DoH resolveNS entry" + ar.cause()); + } + }); + } + + public void example25(Vertx vertx) { + DnsClient client = vertx.createDohClient(); + client + .resolveSRV("vertx.io") + .onComplete(ar -> { + if (ar.succeeded()) { + List records = ar.result(); + for (int i = 0; i < records.size(); i++) { + System.out.printf("DoH resolveSRV (%s): %s\n", i, records.get(i)); + } + } else { + System.out.println("Failed to DoH resolveSRV entry" + ar.cause()); + } + }); + } + + public void example26(Vertx vertx) { + DnsClient client = vertx.createDohClient(); + client + .resolvePTR("1.0.0.10.in-addr.arpa") + .onComplete(ar -> { + if (ar.succeeded()) { + String record = ar.result(); + System.out.printf("DoH resolvePTR result: %s\n", record); + } else { + System.out.println("Failed to DoH resolvePTR entry" + ar.cause()); + } + }); + } + + public void example27(Vertx vertx) { + DnsClient client = vertx.createDohClient(); + client + .reverseLookup("10.0.0.1") + .onComplete(ar -> { + if (ar.succeeded()) { + String record = ar.result(); + System.out.printf("DoH reverseLookup result: %s\n", record); + } else { + System.out.println("Failed to DoH reverseLookup entry" + ar.cause()); + } + }); + } + + public static void main(String[] args) { + Vertx vertx = Vertx.vertx(); + + DNSExamples dnsExamples = new DNSExamples(); + + dnsExamples.example1(vertx); + dnsExamples.example1_(vertx); + dnsExamples.example1__(vertx); + dnsExamples.example2(vertx); + dnsExamples.example3(vertx); + dnsExamples.example4(vertx); + dnsExamples.example5(vertx); + dnsExamples.example6(vertx); + dnsExamples.example7(vertx); + dnsExamples.example8(vertx); + dnsExamples.example10(vertx); + dnsExamples.example11(vertx); + dnsExamples.example12(vertx); + dnsExamples.example14(vertx); + dnsExamples.example15(vertx); + + + dnsExamples.example16(vertx); + dnsExamples.example17(vertx); + dnsExamples.example18(vertx); + dnsExamples.example19(vertx); + dnsExamples.example20(vertx); + dnsExamples.example21(vertx); + dnsExamples.example22(vertx); + dnsExamples.example23(vertx); + dnsExamples.example24(vertx); + dnsExamples.example25(vertx); + dnsExamples.example26(vertx); + dnsExamples.example27(vertx); +// + } } From 481b562ea7d6b0a29a8db40a6038010d5a32d71a Mon Sep 17 00:00:00 2001 From: imz87 Date: Sun, 23 Jun 2024 13:11:14 +0330 Subject: [PATCH 4/9] feat: add DoH tests --- src/test/java/io/vertx/core/dns/DohTest.java | 478 ++++++++++++++++++ .../vertx/test/fakedns/DohMessageEncoder.java | 134 +++++ .../io/vertx/test/fakedns/FakeDNSServer.java | 152 ++++-- 3 files changed, 734 insertions(+), 30 deletions(-) create mode 100644 src/test/java/io/vertx/core/dns/DohTest.java create mode 100644 src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java diff --git a/src/test/java/io/vertx/core/dns/DohTest.java b/src/test/java/io/vertx/core/dns/DohTest.java new file mode 100644 index 00000000000..0ec6381ea10 --- /dev/null +++ b/src/test/java/io/vertx/core/dns/DohTest.java @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.core.dns; + +import io.vertx.core.Vertx; +import io.vertx.core.VertxException; +import io.vertx.core.VertxOptions; +import io.vertx.core.dns.impl.DohClientImpl; +import io.vertx.test.core.TestUtils; +import io.vertx.test.core.VertxTestBase; +import io.vertx.test.fakedns.FakeDNSServer; +import io.vertx.test.netty.TestLoggerFactory; +import org.apache.directory.server.dns.messages.DnsMessage; +import org.apache.directory.server.dns.store.RecordStore; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import static io.vertx.test.core.TestUtils.assertNullPointerException; + +/** + * @author imz.987 + */ +public class DohTest extends VertxTestBase { + + private FakeDNSServer dnsServer; + + @Override + public void setUp() throws Exception { + super.setUp(); + dnsServer = new FakeDNSServer(true, vertx); + dnsServer.start(); + } + + @Override + protected void tearDown() throws Exception { + dnsServer.stop(); + super.tearDown(); + } + + @Test + public void testIllegalArguments() throws Exception { + dnsServer.testResolveAAAA("::1"); + DnsClient dns = prepareDns(); + + assertNullPointerException(() -> dns.lookup(null, ar -> {})); + assertNullPointerException(() -> dns.lookup4(null, ar -> {})); + assertNullPointerException(() -> dns.lookup6(null, ar -> {})); + assertNullPointerException(() -> dns.resolveA(null, ar -> {})); + assertNullPointerException(() -> dns.resolveAAAA(null, ar -> {})); + assertNullPointerException(() -> dns.resolveCNAME(null, ar -> {})); + assertNullPointerException(() -> dns.resolveMX(null, ar -> {})); + assertNullPointerException(() -> dns.resolveTXT(null, ar -> {})); + assertNullPointerException(() -> dns.resolvePTR(null, ar -> {})); + assertNullPointerException(() -> dns.resolveNS(null, ar -> {})); + assertNullPointerException(() -> dns.resolveSRV(null, ar -> {})); + } + + @Test + public void testDefaultDnsClientWithOptions() throws Exception { + int port = 53529; + + VertxOptions vertxOptions = new VertxOptions(); + vertxOptions.getAddressResolverOptions().addServer("127.0.0.1" + ":" + port); + + Vertx vertxWithFakeDns = Vertx.vertx(vertxOptions); + + FakeDNSServer dnsServer2 = new FakeDNSServer(true, vertxWithFakeDns); + dnsServer2.port(port); + dnsServer2.start(); + + Function vertxDnsClientFunction = + vertx -> vertx.createDnsClient(new DnsClientOptions().setSsl(true).setPort(dnsServer2.port()).setHost(dnsServer2.ipAddress())); + + DnsClient dnsClient = vertxDnsClientFunction.apply(vertxWithFakeDns); + + final String ip = "10.0.0.1"; + dnsServer2.testLookup4(ip); + + dnsClient.lookup4("vertx.io", onSuccess(result -> { + assertEquals(ip, result); + testComplete(); + })); + await(); + vertxWithFakeDns.close(); + dnsServer2.stop(); + } + + @Test + public void testResolveA() throws Exception { + final String ip = "10.0.0.1"; + dnsServer.testResolveA(ip); + DnsClient dns = prepareDns(); + + dns.resolveA("vertx.io", onSuccess(result -> { + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + assertEquals(ip, result.get(0)); + ((DohClientImpl) dns).inProgressQueries(num -> { + assertEquals(0, (int)num); + testComplete(); + }); + })); + await(); + } + + @Test + public void testUnresolvedDnsServer() throws Exception { + try { + DnsClient dns = vertx.createDnsClient(new DnsClientOptions().setHost("iamanunresolvablednsserver.com").setPort(53)); + fail(); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + assertEquals("Cannot resolve the host to a valid ip address", e.getMessage()); + } + } + + @Test + public void testResolveAIpV6() throws Exception { + final String ip = "10.0.0.1"; + FakeDNSServer dnsServer2 = new FakeDNSServer(true, vertx); + dnsServer2.ipAddress("::1"); + dnsServer2.start(); + dnsServer2.testResolveA(ip).ipAddress("::1"); + // force the fake dns server to Ipv6 + DnsClient dns = + vertx.createDnsClient(new DnsClientOptions().setSsl(true).setPort(dnsServer2.port()).setHost(dnsServer2.ipAddress())); + dns.resolveA("vertx.io", onSuccess(result -> { + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + assertEquals(ip, result.get(0)); + ((DohClientImpl) dns).inProgressQueries(num -> { + assertEquals(0, (int) num); + testComplete(); + }); + })); + await(); + dnsServer2.stop(); + } + + @Test + public void testResolveAAAA() throws Exception { + dnsServer.testResolveAAAA("::1"); + DnsClient dns = prepareDns(); + + dns.resolveAAAA("vertx.io", onSuccess(result -> { + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + assertEquals("0:0:0:0:0:0:0:1", result.get(0)); + testComplete(); + })); + await(); + } + + @Test + public void testResolveMX() throws Exception { + final String mxRecord = "mail.vertx.io"; + final int prio = 10; + dnsServer.testResolveMX(prio, mxRecord); + DnsClient dns = prepareDns(); + + dns.resolveMX("vertx.io", onSuccess(result -> { + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + MxRecord record = result.get(0); + assertEquals(prio, record.priority()); + assertEquals(record.name(), mxRecord); + testComplete(); + })); + await(); + } + + @Test + public void testResolveTXT() throws Exception { + final String txt = "vertx is awesome"; + dnsServer.testResolveTXT(txt); + DnsClient dns = prepareDns(); + dns.resolveTXT("vertx.io", onSuccess(result -> { + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + assertEquals(txt, result.get(0)); + testComplete(); + })); + await(); + } + + @Test + public void testResolveNS() throws Exception { + final String ns = "ns.vertx.io"; + dnsServer.testResolveNS(ns); + DnsClient dns = prepareDns(); + + dns.resolveNS("vertx.io", onSuccess(result -> { + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + assertEquals(ns, result.get(0)); + testComplete(); + })); + await(); + } + + @Test + public void testResolveCNAME() throws Exception { + final String cname = "cname.vertx.io"; + dnsServer.testResolveCNAME(cname); + DnsClient dns = prepareDns(); + + dns.resolveCNAME("vertx.io", onSuccess(result -> { + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + String record = result.get(0); + assertFalse(record.isEmpty()); + assertEquals(cname, record); + testComplete(); + })); + await(); + } + + @Test + public void testResolvePTR() throws Exception { + final String ptr = "ptr.vertx.io"; + dnsServer.testResolvePTR(ptr); + DnsClient dns = prepareDns(); + + dns.resolvePTR("10.0.0.1.in-addr.arpa", onSuccess(result -> { + assertEquals(ptr, result); + testComplete(); + })); + await(); + } + + @Test + public void testResolveSRV() throws Exception { + final int priority = 10; + final int weight = 1; + final int port = 80; + final String target = "vertx.io"; + + dnsServer.testResolveSRV(priority, weight, port, target); + DnsClient dns = prepareDns(); + + dns.resolveSRV("vertx.io", ar -> { + List result = ar.result(); + assertNotNull(result); + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + + SrvRecord record = result.get(0); + + assertEquals(priority, record.priority()); + assertEquals(weight, record.weight()); + assertEquals(port, record.port()); + assertEquals(target, record.target()); + + testComplete(); + }); + await(); + } + + @Test + public void testLookup4() throws Exception { + final String ip = "10.0.0.1"; + dnsServer.testLookup4(ip); + DnsClient dns = prepareDns(); + dns.lookup4("vertx.io", onSuccess(result -> { + assertEquals(ip, result); + DnsMessage msg = dnsServer.pollMessage(); + assertTrue(msg.isRecursionDesired()); + testComplete(); + })); + await(); + } + + @Test + public void testLookup6() throws Exception { + dnsServer.testLookup6(); + DnsClient dns = prepareDns(); + + dns.lookup6("vertx.io", onSuccess(result -> { + assertEquals("0:0:0:0:0:0:0:1", result); + testComplete(); + })); + await(); + } + + @Test + public void testLookup() throws Exception { + String ip = "10.0.0.1"; + dnsServer.testLookup(ip); + DnsClient dns = prepareDns(); + + dns.lookup("vertx.io", onSuccess(result -> { + assertEquals(ip, result); + testComplete(); + })); + await(); + } + + @Test + public void testTimeout() throws Exception { + DnsClient dns = vertx.createDnsClient(new DnsClientOptions().setSsl(true).setHost("localhost").setPort(10000).setQueryTimeout(1)); + + dns.lookup("vertx.io", onFailure(result -> { + assertEquals(VertxException.class, result.getClass()); + assertEquals("DNS query timeout for vertx.io.", result.getMessage()); + ((DohClientImpl) dns).inProgressQueries(num -> { + assertEquals(0, (int)num); + testComplete(); + }); + })); + await(); + } + + @Test + public void testLookupNonExisting() throws Exception { + dnsServer.testLookupNonExisting(); + DnsClient dns = prepareDns(); + dns.lookup("gfegjegjf.sg1", ar -> { + DnsException cause = (DnsException)ar.cause(); + assertEquals(DnsResponseCode.NXDOMAIN, cause.code()); + testComplete(); + }); + await(); + } + + @Test + public void testReverseLookupIpv4() throws Exception { + String address = "10.0.0.1"; + String ptr = "ptr.vertx.io"; + dnsServer.testReverseLookup(ptr); + DnsClient dns = prepareDns(); + + dns.reverseLookup(address, onSuccess(result -> { + assertEquals(ptr, result); + testComplete(); + })); + await(); + } + + @Test + public void testReverseLookupIpv6() throws Exception { + String ptr = "ptr.vertx.io"; + dnsServer.testReverseLookup(ptr); + DnsClient dns = prepareDns(); + + dns.reverseLookup("::1", onSuccess(result -> { + assertEquals(ptr, result); + testComplete(); + })); + await(); + } + + @Test + public void testLookup4CNAME() throws Exception { + final String cname = "cname.vertx.io"; + final String ip = "10.0.0.1"; + dnsServer.testLookup4CNAME(cname, ip); + DnsClient dns = prepareDns(); + + dns.lookup4("vertx.io", onSuccess(result -> { + assertEquals(ip, result); + testComplete(); + })); + await(); + } + + @Test + public void testResolveMXWhenDNSRepliesWithDNAMERecord() throws Exception { + final DnsClient dns = prepareDns(); + dnsServer.testResolveDNAME("mail.vertx.io"); + + dns.resolveMX("vertx.io") + .onComplete(ar -> { + assertTrue(ar.failed()); + testComplete(); + }); + await(); + } + + private TestLoggerFactory testLogging(DnsClientOptions options) { + final String ip = "10.0.0.1"; + dnsServer.testResolveA(ip); + return TestUtils.testLogging(() -> { + try { + prepareDns(options) + .resolveA(ip, fut -> { + testComplete(); + }); + await(); + } catch (Exception e) { + fail(e); + } + }); + } + + @Test + public void testLogActivity() throws Exception { + TestLoggerFactory factory = testLogging(new DnsClientOptions().setSsl(true).setLogActivity(true)); + assertTrue(factory.hasName("io.netty.handler.logging.LoggingHandler")); + } + + @Test + public void testDoNotLogActivity() throws Exception { + TestLoggerFactory factory = testLogging(new DnsClientOptions().setSsl(true).setLogActivity(false)); + assertFalse(factory.hasName("io.netty.handler.logging.LoggingHandler")); + } + + @Test + public void testRecursionDesired() throws Exception { + final String ip = "10.0.0.1"; + + dnsServer.testResolveA(ip); + DnsClient dns = prepareDns(new DnsClientOptions().setSsl(true).setRecursionDesired(true)); + dns.resolveA("vertx.io", onSuccess(result -> { + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + assertEquals(ip, result.get(0)); + DnsMessage msg = dnsServer.pollMessage(); + assertTrue(msg.isRecursionDesired()); + ((DohClientImpl) dns).inProgressQueries(num -> { + assertEquals(0, (int)num); + testComplete(); + }); + })); + await(); + } + + @Test + public void testClose() throws Exception { + waitFor(2); + String ip = "10.0.0.1"; + RecordStore store = dnsServer.testResolveA(ip).store(); + CountDownLatch latch1 = new CountDownLatch(1); + CountDownLatch latch2 = new CountDownLatch(1); + dnsServer.store(question -> { + latch1.countDown(); + try { + latch2.await(10, TimeUnit.SECONDS); + } catch (Exception e) { + fail(e); + } + return store.getRecords(question); + }); + DnsClient dns = prepareDns(new DnsClientOptions().setSsl(true).setQueryTimeout(15000)); + dns + .resolveA("vertx.io") + .onComplete(onFailure(timeout -> { + assertTrue(timeout.getMessage().contains("closed")); + complete(); + })); + awaitLatch(latch1); + dns.close().onComplete(onSuccess(v -> { + complete(); + latch2.countDown(); + })); + await(); + } + + private DnsClient prepareDns() throws Exception { + return prepareDns(new DnsClientOptions().setSsl(true).setQueryTimeout(15000)); + } + + private DnsClient prepareDns(DnsClientOptions options) throws Exception { + return vertx.createDnsClient(new DnsClientOptions(options).setPort(dnsServer.port()).setHost(dnsServer.ipAddress())); + } +} diff --git a/src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java b/src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java new file mode 100644 index 00000000000..426d502a2ec --- /dev/null +++ b/src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java @@ -0,0 +1,134 @@ +package io.vertx.test.fakedns; + +import io.vertx.core.dns.MxRecord; +import io.vertx.core.dns.dnsrecord.DohResourceRecord; +import io.vertx.core.dns.impl.MxRecordImpl; +import org.apache.directory.server.dns.messages.RecordType; +import org.apache.directory.server.dns.messages.ResourceRecord; +import org.apache.directory.server.dns.store.DnsAttribute; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @author imz.987 + */ +public class DohMessageEncoder { + /** + * the log for this class + */ + private static final Logger log = LoggerFactory.getLogger(DohMessageEncoder.class); + + private static abstract class RecordEncoder { + protected abstract DohResourceRecord encode(DohResourceRecord dohResourceRecord, ResourceRecord resourceRecord); + + public DohResourceRecord myEncode(ResourceRecord resourceRecord) { + DohResourceRecord dohResourceRecord = new DohResourceRecord(); + dohResourceRecord.setTtl(resourceRecord.getTimeToLive()); + dohResourceRecord.setName(resourceRecord.getDomainName()); + dohResourceRecord.setData(resourceRecord.toString()); + dohResourceRecord.setType(resourceRecord.getRecordType().convert()); + + return encode(dohResourceRecord, resourceRecord); + } + } + + private static final RecordEncoder DEFAULT = new RecordEncoder() { + @Override + protected DohResourceRecord encode(DohResourceRecord dohResourceRecord, ResourceRecord resourceRecord) { + dohResourceRecord.setData(resourceRecord.get(DnsAttribute.DOMAIN_NAME)); + return dohResourceRecord; + } + }; + private static final RecordEncoder OTHER = new RecordEncoder() { + @Override + protected DohResourceRecord encode(DohResourceRecord dohResourceRecord, ResourceRecord resourceRecord) { + dohResourceRecord.setData(resourceRecord.get(DnsAttribute.IP_ADDRESS)); + return dohResourceRecord; + } + }; + + private static final RecordEncoder TXT = new RecordEncoder() { + @Override + protected DohResourceRecord encode(DohResourceRecord dohResourceRecord, ResourceRecord resourceRecord) { + dohResourceRecord.setData(resourceRecord.get(DnsAttribute.CHARACTER_STRING)); + return dohResourceRecord; + } + }; + + private static final RecordEncoder AAAA = new RecordEncoder() { + @Override + protected DohResourceRecord encode(DohResourceRecord dohResourceRecord, ResourceRecord resourceRecord) { + if (resourceRecord.get(DnsAttribute.IP_ADDRESS).equals("::1")) { + dohResourceRecord.setData("0:0:0:0:0:0:0:1"); + } else { + dohResourceRecord.setData(resourceRecord.get(DnsAttribute.IP_ADDRESS)); + } + return dohResourceRecord; + } + }; + + private static final RecordEncoder MX = new RecordEncoder() { + @Override + protected DohResourceRecord encode(DohResourceRecord dohResourceRecord, ResourceRecord resourceRecord) { + MxRecord mxRecord = new MxRecordImpl(Integer.parseInt(resourceRecord.get(DnsAttribute.MX_PREFERENCE)), + resourceRecord.get(DnsAttribute.DOMAIN_NAME)); + dohResourceRecord.setData(String.format("[%s]", mxRecord)); + return dohResourceRecord; + } + }; + + private static final RecordEncoder SRV = new RecordEncoder() { + @Override + protected DohResourceRecord encode(DohResourceRecord dohResourceRecord, ResourceRecord resourceRecord) { + dohResourceRecord.setTtl(resourceRecord.getTimeToLive()); + dohResourceRecord.setName(resourceRecord.getDomainName()); + dohResourceRecord.setType(resourceRecord.getRecordType().convert()); + + int priority = Integer.parseInt(resourceRecord.get(DnsAttribute.SERVICE_PRIORITY)); + int weight = Integer.parseInt(resourceRecord.get(DnsAttribute.SERVICE_WEIGHT)); + int port1 = Integer.parseInt(resourceRecord.get(DnsAttribute.SERVICE_PORT)); + String name = "io."; + String protocol = "vertx"; + String service = "dns"; + String target = "vertx.io"; + + dohResourceRecord.setData(String.join(" ", String.valueOf(priority), + String.valueOf(weight), String.valueOf(port1), name, protocol, + service, target)); + + return dohResourceRecord; + } + }; + + + /** + * A Hashed Adapter mapping record types to their encoders. + */ + private static final Map DEFAULT_ENCODERS; + + static { + Map map = new HashMap<>(); + + map.put(RecordType.SOA, DohMessageEncoder.OTHER); + map.put(RecordType.A, DohMessageEncoder.OTHER); + map.put(RecordType.AAAA, DohMessageEncoder.AAAA); + map.put(RecordType.NS, DohMessageEncoder.DEFAULT); + map.put(RecordType.CNAME, DohMessageEncoder.DEFAULT); + map.put(RecordType.PTR, DohMessageEncoder.DEFAULT); + map.put(RecordType.MX, DohMessageEncoder.MX); + map.put(RecordType.SRV, DohMessageEncoder.SRV); + map.put(RecordType.TXT, DohMessageEncoder.TXT); + map.put(RecordType.DNAME, DohMessageEncoder.OTHER); + + DEFAULT_ENCODERS = Collections.unmodifiableMap(map); + } + + public static DohResourceRecord encode(ResourceRecord resourceRecord) { + return DEFAULT_ENCODERS.get(resourceRecord.getRecordType()).myEncode(resourceRecord); + } + +} diff --git a/src/test/java/io/vertx/test/fakedns/FakeDNSServer.java b/src/test/java/io/vertx/test/fakedns/FakeDNSServer.java index ab87564fed4..dd390eedc71 100644 --- a/src/test/java/io/vertx/test/fakedns/FakeDNSServer.java +++ b/src/test/java/io/vertx/test/fakedns/FakeDNSServer.java @@ -11,16 +11,17 @@ package io.vertx.test.fakedns; +import io.netty.handler.codec.dns.DnsRecordType; +import io.vertx.core.Vertx; +import io.vertx.core.dns.DnsResponseCode; +import io.vertx.core.dns.dnsrecord.DohRecord; +import io.vertx.core.dns.dnsrecord.DohResourceRecord; +import io.vertx.core.http.*; +import io.vertx.test.tls.Trust; import org.apache.directory.server.dns.DnsException; import org.apache.directory.server.dns.DnsServer; import org.apache.directory.server.dns.io.encoder.ResourceRecordEncoder; -import org.apache.directory.server.dns.messages.DnsMessage; -import org.apache.directory.server.dns.messages.DnsMessageModifier; -import org.apache.directory.server.dns.messages.QuestionRecord; -import org.apache.directory.server.dns.messages.RecordClass; -import org.apache.directory.server.dns.messages.RecordType; -import org.apache.directory.server.dns.messages.ResourceRecord; -import org.apache.directory.server.dns.messages.ResourceRecordModifier; +import org.apache.directory.server.dns.messages.*; import org.apache.directory.server.dns.protocol.DnsProtocolHandler; import org.apache.directory.server.dns.protocol.DnsTcpDecoder; import org.apache.directory.server.dns.protocol.DnsUdpDecoder; @@ -33,11 +34,7 @@ import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.session.IoSession; -import org.apache.mina.filter.codec.ProtocolCodecFactory; -import org.apache.mina.filter.codec.ProtocolCodecFilter; -import org.apache.mina.filter.codec.ProtocolDecoder; -import org.apache.mina.filter.codec.ProtocolEncoder; -import org.apache.mina.filter.codec.ProtocolEncoderOutput; +import org.apache.mina.filter.codec.*; import org.apache.mina.transport.socket.DatagramSessionConfig; import java.io.IOException; @@ -88,8 +85,17 @@ public static RecordStore A_store(Function entries) { private volatile RecordStore store; private List acceptors; private final Deque currentMessage = new ArrayDeque<>(); + private final boolean ssl; + private Vertx vertx; + private HttpServer httpServer; public FakeDNSServer() { + this.ssl = false; + } + + public FakeDNSServer(boolean ssl, Vertx vertx) { + this.ssl = ssl; + this.vertx = vertx; } public RecordStore store() { @@ -114,11 +120,19 @@ public FakeDNSServer ipAddress(String ipAddress) { return this; } + public String ipAddress() { + return this.ipAddress; + } + public FakeDNSServer port(int p) { port = p; return this; } + public int port() { + return this.port; + } + public FakeDNSServer testResolveA(final String ipAddress) { return testResolveA(Collections.singletonMap("dns.vertx.io", ipAddress)); } @@ -357,7 +371,7 @@ public FakeDNSServer testLookup4CNAME(final String cname, final String ip) { return store(new RecordStore() { @Override public Set getRecords(QuestionRecord questionRecord) - throws org.apache.directory.server.dns.DnsException { + throws org.apache.directory.server.dns.DnsException { // use LinkedHashSet since the order of the result records has to be preserved to make sure the unit test fails Set set = new LinkedHashSet<>(); @@ -384,6 +398,18 @@ public Set getRecords(QuestionRecord questionRecord) @Override public void start() throws IOException { + if (this.ssl) { + HttpServerOptions options = new HttpServerOptions() + .setSsl(true) + .setPort(port) + .setHost(ipAddress) + .setKeyCertOptions(Trust.DOH_JKS_HOST.get()); + + httpServer = vertx.createHttpServer(options); + httpServer.requestHandler(this::simulateDohServer).listen(); + + return; + } DnsProtocolHandler handler = new DnsProtocolHandler(this, new RecordStore() { @Override @@ -400,9 +426,9 @@ public Set getRecords(QuestionRecord question) throws DnsExcepti public void sessionCreated(IoSession session) throws Exception { // Use our own codec to support AAAA testing if (session.getTransportMetadata().isConnectionless()) { - session.getFilterChain().addFirst( "codec", new ProtocolCodecFilter(new TestDnsProtocolUdpCodecFactory())); + session.getFilterChain().addFirst("codec", new ProtocolCodecFilter(new TestDnsProtocolUdpCodecFactory())); } else { - session.getFilterChain().addFirst( "codec", new ProtocolCodecFilter(new TestDnsProtocolTcpCodecFactory())); + session.getFilterChain().addFirst("codec", new ProtocolCodecFilter(new TestDnsProtocolTcpCodecFactory())); } } @@ -418,13 +444,13 @@ public void messageReceived(IoSession session, Object message) { }; UdpTransport udpTransport = new UdpTransport(ipAddress, port); - ((DatagramSessionConfig)udpTransport.getAcceptor().getSessionConfig()).setReuseAddress(true); + ((DatagramSessionConfig) udpTransport.getAcceptor().getSessionConfig()).setReuseAddress(true); TcpTransport tcpTransport = new TcpTransport(ipAddress, port); tcpTransport.getAcceptor().getSessionConfig().setReuseAddress(true); setTransports(udpTransport, tcpTransport); - for (Transport transport : getTransports()) { + for (Transport transport : getTransports()) { IoAcceptor acceptor = transport.getAcceptor(); acceptor.setHandler(handler); @@ -434,10 +460,76 @@ public void messageReceived(IoSession session, Object message) { } } + private void simulateDohServer(HttpServerRequest request) { + if (HttpMethod.GET != request.method() || + !request.getHeader("accept").equalsIgnoreCase("application/dns-json")) { + HttpServerResponse response = request.response(); + response.setStatusCode(400); + response.send("DoH Request is not correct!"); + return; + } + + QuestionRecord questionRecord = createQuestionRecord(request); + + cacheDnsMessage(questionRecord); + + Set resourceRecords = getStoredRecords(questionRecord); + List answers = + resourceRecords.stream().map(DohMessageEncoder::encode).collect(Collectors.toList()); + + if (answers.isEmpty()) { + DohRecord dohRecord = new DohRecord(); + dohRecord.setStatus(DnsResponseCode.NXDOMAIN.code()); + sendResponse(dohRecord, request.response()); + return; + } + + DohRecord dohRecord = new DohRecord(); + dohRecord.setStatus(0); + dohRecord.setAnswer(answers); + sendResponse(dohRecord, request.response()); + } + + private QuestionRecord createQuestionRecord(HttpServerRequest request) { + String domainName = request.getParam("name"); + RecordType type = RecordType.convert((short) DnsRecordType.valueOf(request.getParam("type")).intValue()); + return new QuestionRecord(domainName, type, RecordClass.IN); + } + + private void cacheDnsMessage(QuestionRecord questionRecord) { + DnsMessageModifier dnsMessageModifier = new DnsMessageModifier(); + dnsMessageModifier.setMessageType(MessageType.QUERY); + dnsMessageModifier.setRecursionDesired(true); + dnsMessageModifier.setQuestionRecords(Collections.singletonList(questionRecord)); + currentMessage.add(dnsMessageModifier.getDnsMessage()); + } + + private Set getStoredRecords(QuestionRecord questionRecord) { + Set records = null; + try { + records = store.getRecords(questionRecord); + } catch (DnsException ignored) { + } + if (records != null) { + return records; + } + return new HashSet<>(); + } + + private void sendResponse(DohRecord record, HttpServerResponse response) { + response.putHeader("content-type", "application/dns-json"); + response.setStatusCode(200); + response.send(record.toJson().toBuffer()); + } + @Override public void stop() { - for (Transport transport : getTransports()) { - transport.getAcceptor().dispose(); + if (this.ssl) { + this.httpServer.close(); + } else { + for (Transport transport : getTransports()) { + transport.getAcceptor().dispose(); + } } } @@ -529,7 +621,7 @@ private void encode(DnsMessage dnsMessage, IoBuffer buf) { encoder.encode(buf, dnsMessage); - for (ResourceRecord record: dnsMessage.getAnswerRecords()) { + for (ResourceRecord record : dnsMessage.getAnswerRecords()) { // This is a hack to allow to also test for AAAA resolution as DnsMessageEncoder does not support it and it // is hard to extend, because the interesting methods are private... // In case of RecordType.AAAA we need to encode the RecordType by ourself @@ -554,10 +646,10 @@ public ProtocolEncoder getEncoder(IoSession session) throws Exception { @Override public void encode(IoSession session, Object message, ProtocolEncoderOutput out) { - IoBuffer buf = IoBuffer.allocate( 1024 ); - FakeDNSServer.this.encode((DnsMessage)message, buf); + IoBuffer buf = IoBuffer.allocate(1024); + FakeDNSServer.this.encode((DnsMessage) message, buf); buf.flip(); - out.write( buf ); + out.write(buf); } }; } @@ -578,17 +670,17 @@ public ProtocolEncoder getEncoder(IoSession session) throws Exception { @Override public void encode(IoSession session, Object message, ProtocolEncoderOutput out) { - IoBuffer buf = IoBuffer.allocate( 1024 ); - buf.putShort( ( short ) 0 ); + IoBuffer buf = IoBuffer.allocate(1024); + buf.putShort((short) 0); FakeDNSServer.this.encode((DnsMessage) message, buf); - encoder.encode( buf, ( DnsMessage ) message ); + encoder.encode(buf, (DnsMessage) message); int end = buf.position(); - short recordLength = ( short ) ( end - 2 ); + short recordLength = (short) (end - 2); buf.rewind(); - buf.putShort( recordLength ); - buf.position( end ); + buf.putShort(recordLength); + buf.position(end); buf.flip(); - out.write( buf ); + out.write(buf); } }; } From 406babb42ac4d82799b605ec04be57e53b146970 Mon Sep 17 00:00:00 2001 From: imz87 Date: Sun, 23 Jun 2024 15:40:37 +0330 Subject: [PATCH 5/9] feat: add DoH tests --- src/test/java/io/vertx/core/dns/DohTest.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/test/java/io/vertx/core/dns/DohTest.java b/src/test/java/io/vertx/core/dns/DohTest.java index 0ec6381ea10..e3130dde6e5 100644 --- a/src/test/java/io/vertx/core/dns/DohTest.java +++ b/src/test/java/io/vertx/core/dns/DohTest.java @@ -1,14 +1,3 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - package io.vertx.core.dns; import io.vertx.core.Vertx; From 45571889c9c42939a9b76d9abd5b98997c1b348d Mon Sep 17 00:00:00 2001 From: imz87 Date: Sun, 23 Jun 2024 16:40:28 +0330 Subject: [PATCH 6/9] feat: add DoH tests --- src/test/java/io/vertx/core/dns/DohTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/io/vertx/core/dns/DohTest.java b/src/test/java/io/vertx/core/dns/DohTest.java index e3130dde6e5..7eaf272a58e 100644 --- a/src/test/java/io/vertx/core/dns/DohTest.java +++ b/src/test/java/io/vertx/core/dns/DohTest.java @@ -60,7 +60,6 @@ public void testIllegalArguments() throws Exception { @Test public void testDefaultDnsClientWithOptions() throws Exception { int port = 53529; - VertxOptions vertxOptions = new VertxOptions(); vertxOptions.getAddressResolverOptions().addServer("127.0.0.1" + ":" + port); From 1dcbab45ccb0c7eae54317e77c8f33823752cbde Mon Sep 17 00:00:00 2001 From: imz87 Date: Sun, 23 Jun 2024 16:55:05 +0330 Subject: [PATCH 7/9] feat: use correct email --- src/main/java/io/vertx/core/dns/impl/DohClientImpl.java | 2 +- .../java/io/vertx/core/dns/impl/decoder/DohRecordDecoder.java | 2 +- src/test/java/io/vertx/core/dns/DohTest.java | 2 +- src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/vertx/core/dns/impl/DohClientImpl.java b/src/main/java/io/vertx/core/dns/impl/DohClientImpl.java index 57cf9a6aabf..7db05c21ff2 100644 --- a/src/main/java/io/vertx/core/dns/impl/DohClientImpl.java +++ b/src/main/java/io/vertx/core/dns/impl/DohClientImpl.java @@ -28,7 +28,7 @@ import static io.vertx.core.dns.impl.decoder.JsonHelper.appendDotIfRequired; /** - * @author imz.987 + * @author imz87 */ public class DohClientImpl extends BaseDnsClientImpl { public static final String DOH_URL_FORMAT = "/dns-query?name=%s&type=%s"; diff --git a/src/main/java/io/vertx/core/dns/impl/decoder/DohRecordDecoder.java b/src/main/java/io/vertx/core/dns/impl/decoder/DohRecordDecoder.java index fc639469c36..ac537c566f7 100644 --- a/src/main/java/io/vertx/core/dns/impl/decoder/DohRecordDecoder.java +++ b/src/main/java/io/vertx/core/dns/impl/decoder/DohRecordDecoder.java @@ -13,7 +13,7 @@ import java.util.function.Function; /** - * @author imz.987 + * @author imz87 */ public class DohRecordDecoder { private static final Logger log = LoggerFactory.getLogger(DohRecordDecoder.class); diff --git a/src/test/java/io/vertx/core/dns/DohTest.java b/src/test/java/io/vertx/core/dns/DohTest.java index 7eaf272a58e..0290f519080 100644 --- a/src/test/java/io/vertx/core/dns/DohTest.java +++ b/src/test/java/io/vertx/core/dns/DohTest.java @@ -20,7 +20,7 @@ import static io.vertx.test.core.TestUtils.assertNullPointerException; /** - * @author imz.987 + * @author imz87 */ public class DohTest extends VertxTestBase { diff --git a/src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java b/src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java index 426d502a2ec..9db7374250b 100644 --- a/src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java +++ b/src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java @@ -14,7 +14,7 @@ import java.util.Map; /** - * @author imz.987 + * @author imz87 */ public class DohMessageEncoder { /** From 97aa10184d261250c3b87492f872f6b8b22ef617 Mon Sep 17 00:00:00 2001 From: imz87 Date: Sun, 23 Jun 2024 17:05:05 +0330 Subject: [PATCH 8/9] feat: use correct email --- src/test/java/io/vertx/core/dns/DohTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/vertx/core/dns/DohTest.java b/src/test/java/io/vertx/core/dns/DohTest.java index 0290f519080..b85eac1acc9 100644 --- a/src/test/java/io/vertx/core/dns/DohTest.java +++ b/src/test/java/io/vertx/core/dns/DohTest.java @@ -111,7 +111,7 @@ public void testUnresolvedDnsServer() throws Exception { fail(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); - assertEquals("Cannot resolve the host to a valid ip address", e.getMessage()); + assertEquals("Cant resolve the host to a valid ip address", e.getMessage()); } } From 497af9fc401016bd0fd033e23ef4466a057cb1bc Mon Sep 17 00:00:00 2001 From: imz87 Date: Thu, 27 Jun 2024 11:46:45 +0330 Subject: [PATCH 9/9] feat: correct author and email --- src/main/java/io/vertx/core/dns/dnsrecord/DohRecord.java | 4 ++++ .../java/io/vertx/core/dns/dnsrecord/DohResourceRecord.java | 4 ++++ src/main/java/io/vertx/core/dns/dnsrecord/Question.java | 4 ++++ src/main/java/io/vertx/core/dns/impl/BaseDnsClientImpl.java | 5 ++++- src/main/java/io/vertx/core/dns/impl/DohClientImpl.java | 3 ++- .../io/vertx/core/dns/impl/decoder/DohRecordDecoder.java | 5 ++++- src/main/java/io/vertx/core/dns/impl/decoder/JsonHelper.java | 4 ++++ src/test/java/io/vertx/core/dns/DohTest.java | 2 +- src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java | 5 ++++- src/test/java/io/vertx/test/fakedns/FakeDNSServer.java | 1 + 10 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/vertx/core/dns/dnsrecord/DohRecord.java b/src/main/java/io/vertx/core/dns/dnsrecord/DohRecord.java index deae9a8db98..dbffef78839 100644 --- a/src/main/java/io/vertx/core/dns/dnsrecord/DohRecord.java +++ b/src/main/java/io/vertx/core/dns/dnsrecord/DohRecord.java @@ -6,6 +6,10 @@ import java.util.List; +/** + * @author Iman Zolfaghari + */ + @DataObject @JsonGen(publicConverter = false) public class DohRecord { diff --git a/src/main/java/io/vertx/core/dns/dnsrecord/DohResourceRecord.java b/src/main/java/io/vertx/core/dns/dnsrecord/DohResourceRecord.java index 119aa278056..976936fea0c 100644 --- a/src/main/java/io/vertx/core/dns/dnsrecord/DohResourceRecord.java +++ b/src/main/java/io/vertx/core/dns/dnsrecord/DohResourceRecord.java @@ -4,6 +4,10 @@ import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; +/** + * @author Iman Zolfaghari + */ + @DataObject @JsonGen(publicConverter = false) public class DohResourceRecord { diff --git a/src/main/java/io/vertx/core/dns/dnsrecord/Question.java b/src/main/java/io/vertx/core/dns/dnsrecord/Question.java index 7a6b10c362a..8603e0d5cbd 100644 --- a/src/main/java/io/vertx/core/dns/dnsrecord/Question.java +++ b/src/main/java/io/vertx/core/dns/dnsrecord/Question.java @@ -4,6 +4,10 @@ import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; +/** + * @author Iman Zolfaghari + */ + @DataObject @JsonGen(publicConverter = false) public class Question { diff --git a/src/main/java/io/vertx/core/dns/impl/BaseDnsClientImpl.java b/src/main/java/io/vertx/core/dns/impl/BaseDnsClientImpl.java index 917ee5253e9..e09735e09f8 100644 --- a/src/main/java/io/vertx/core/dns/impl/BaseDnsClientImpl.java +++ b/src/main/java/io/vertx/core/dns/impl/BaseDnsClientImpl.java @@ -18,7 +18,10 @@ import java.util.List; /** - * @author imz + * + * Base class for both DnsClient and DohClient, containing some common methods shared between them. + * + * @author Iman Zolfaghari */ diff --git a/src/main/java/io/vertx/core/dns/impl/DohClientImpl.java b/src/main/java/io/vertx/core/dns/impl/DohClientImpl.java index 7db05c21ff2..5e4074e7fbe 100644 --- a/src/main/java/io/vertx/core/dns/impl/DohClientImpl.java +++ b/src/main/java/io/vertx/core/dns/impl/DohClientImpl.java @@ -28,8 +28,9 @@ import static io.vertx.core.dns.impl.decoder.JsonHelper.appendDotIfRequired; /** - * @author imz87 + * @author Iman Zolfaghari */ + public class DohClientImpl extends BaseDnsClientImpl { public static final String DOH_URL_FORMAT = "/dns-query?name=%s&type=%s"; private final HttpClient httpClient; diff --git a/src/main/java/io/vertx/core/dns/impl/decoder/DohRecordDecoder.java b/src/main/java/io/vertx/core/dns/impl/decoder/DohRecordDecoder.java index ac537c566f7..3a601c63c3a 100644 --- a/src/main/java/io/vertx/core/dns/impl/decoder/DohRecordDecoder.java +++ b/src/main/java/io/vertx/core/dns/impl/decoder/DohRecordDecoder.java @@ -13,7 +13,10 @@ import java.util.function.Function; /** - * @author imz87 + * + * Handles the decoding of DoH records. + * + * @author Iman Zolfaghari */ public class DohRecordDecoder { private static final Logger log = LoggerFactory.getLogger(DohRecordDecoder.class); diff --git a/src/main/java/io/vertx/core/dns/impl/decoder/JsonHelper.java b/src/main/java/io/vertx/core/dns/impl/decoder/JsonHelper.java index 1056c44067f..517d58d02bd 100644 --- a/src/main/java/io/vertx/core/dns/impl/decoder/JsonHelper.java +++ b/src/main/java/io/vertx/core/dns/impl/decoder/JsonHelper.java @@ -4,6 +4,10 @@ import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; +/** + * @author Iman Zolfaghari + */ + public class JsonHelper { public static T normalizePropertyNames(T obj) { if (obj instanceof JsonObject) { diff --git a/src/test/java/io/vertx/core/dns/DohTest.java b/src/test/java/io/vertx/core/dns/DohTest.java index b85eac1acc9..5c04f157c2a 100644 --- a/src/test/java/io/vertx/core/dns/DohTest.java +++ b/src/test/java/io/vertx/core/dns/DohTest.java @@ -20,7 +20,7 @@ import static io.vertx.test.core.TestUtils.assertNullPointerException; /** - * @author imz87 + * @author Iman Zolfaghari */ public class DohTest extends VertxTestBase { diff --git a/src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java b/src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java index 9db7374250b..55ed9ace9e1 100644 --- a/src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java +++ b/src/test/java/io/vertx/test/fakedns/DohMessageEncoder.java @@ -14,8 +14,11 @@ import java.util.Map; /** - * @author imz87 + * An encoder for DoH messages. + * + * @author Iman Zolfaghari */ + public class DohMessageEncoder { /** * the log for this class diff --git a/src/test/java/io/vertx/test/fakedns/FakeDNSServer.java b/src/test/java/io/vertx/test/fakedns/FakeDNSServer.java index dd390eedc71..96c5ea0dfe1 100644 --- a/src/test/java/io/vertx/test/fakedns/FakeDNSServer.java +++ b/src/test/java/io/vertx/test/fakedns/FakeDNSServer.java @@ -45,6 +45,7 @@ /** * @author Norman Maurer + * @author Iman Zolfaghari */ public final class FakeDNSServer extends DnsServer {