diff --git a/pom.xml b/pom.xml
index acead98..92fd48e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,6 +49,7 @@
jDnsProxy
+ xmpp-dox
${project.artifactId}
diff --git a/readme.md b/readme.md
index 5e686e0..f59d31e 100644
--- a/readme.md
+++ b/readme.md
@@ -14,18 +14,22 @@ Sample/default configuration is in [jdnsproxy.properties](https://github.com/mop
Build/run like so:
```
mvn clean package
-java -jar target/jDnsProxy.jar ./jdnsproxy.properties
+java -jar jDnsProxy/target/jDnsProxy.jar ./jdnsproxy.properties
+
+# or with xmpp:// listener+resolver support:
+java -cp jDnsProxy/target/jDnsProxy.jar:xmpp-dox/target/xmpp-dox.jar com.moparisthebest.dns.DnsProxy xmpp-dox/jdnsproxy.xmpp.resolver.properties
```
Implemented specs:
* [RFC-1035: DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION](https://tools.ietf.org/html/rfc1035)
* [RFC-7858: Specification for DNS over Transport Layer Security (TLS)](https://tools.ietf.org/html/rfc7858)
- * [Draft: DNS Queries over HTTPS](https://tools.ietf.org/html/draft-hoffman-dns-over-https)
+ * [RFC 8484: DNS Queries over HTTPS (DoH)](http://tools.ietf.org/html/rfc8484)
* [Draft: Serving Stale Data to Improve DNS Resiliency](https://tools.ietf.org/html/draft-ietf-dnsop-serve-stale)
* [RFC-6891: Extension Mechanisms for DNS (EDNS(0))](https://tools.ietf.org/html/rfc6891)
* [DNS EDNS0 Option Codes (OPT)](https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11)
* [RFC-3225: Indicating Resolver Support of DNSSEC](https://tools.ietf.org/html/rfc3225)
+ * [XEP-xxxx: DNS Queries over XMPP (DoX)](https://xmpp.org/extensions/inbox/dox.html)
Use these for quick testing:
```
diff --git a/xmpp-dox/dox.md b/xmpp-dox/dox.md
new file mode 100644
index 0000000..8edfdfa
--- /dev/null
+++ b/xmpp-dox/dox.md
@@ -0,0 +1,34 @@
+XEP-XXXX DNS Queries over XMPP (DoX)
+------------------------------------
+
+Submitted [XEP](https://xmpp.org/extensions/inbox/dox.html)
+
+```
+# put your jid+pass details in jdns.xmpp.resolver.properties
+$ java -jar jDnsProxy.jar jdns.xmpp.resolver.properties &
+$ dig -p5353 @127.0.0.1 +short +tcp example.org
+93.184.216.34
+```
+
+wire format of protocol (this is the request+response from the query above, A record for example.org):
+
+request:
+```xml
+
+ vOIBIAABAAAAAAABB2V4YW1wbGUDb3JnAAABAAEAACkQAAAAAAAADAAKAAj5HO5JuEe+mA
+
+```
+
+response:
+```xml
+
+ vOKBoAABAAEAAAABB2V4YW1wbGUDb3JnAAABAAHADAABAAEAAAhjAARduNgiAAApEAAAAAAAAAA
+
+```
+
+The content of the dns element is the DNS on-the-wire format is defined in [RFC1035].
+The body MUST be encoded with base64 [RFC4648].
+Padding characters for base64 MUST NOT be included.
+
+[RFC1035]: https://tools.ietf.org/html/rfc1035
+[RFC4648]: https://tools.ietf.org/html/rfc4648
diff --git a/xmpp-dox/jdnsproxy.xmpp.listener.properties b/xmpp-dox/jdnsproxy.xmpp.listener.properties
new file mode 100644
index 0000000..dd0fd46
--- /dev/null
+++ b/xmpp-dox/jdnsproxy.xmpp.listener.properties
@@ -0,0 +1,52 @@
+# minTtl: rewrite TTLs lower than this to this value, default 600, 0 disables this feature
+minTtl=600
+
+# staleResponseTimeout: milliseconds to wait for response to query before serving a stale record if we have it, default 1000
+staleResponseTimeout=1000
+# staleResponseTtl: TTL to apply to stale record when above timeout is met and stale record is served, default 10
+staleResponseTtl=10
+
+# cacheFile: path to file to persist cache to at an interval
+cacheFile=dnscache.map
+# cacheDelayMinutes: how often to write the cache to disk
+cacheDelayMinutes=60
+
+# packetQueueLength: maximum requests queued waiting for responses from upstream, all resolvers specified process from this queue, cached responses don't enter this queue, default 100, 0 means unlimited
+packetQueueLength=100
+
+# listeners: list of listeners, currently supports tcp:// and udp:// with no options, default 'tcp://127.0.0.1:5353 udp://127.0.0.1:5353'
+# also supports xmpp:// (DNS-over-XMPP), put IP:port of XMPP server, along with username/password to login with
+listeners=tcp://127.0.0.1:5353 udp://127.0.0.1:5353 xmpp://208.68.163.210:5222#user=anyjid@example.org/listener;pass=y0urPa55W0rDHere
+
+# resolvers: list of resolvers with or without options, whitespace separated, options are in fragment separated by ;
+# currently support tcp:// (regular DNS-over-TCP), tls:// (DNS-over-TLS), http:// https:// (DNS-over-HTTPS)
+# both tls:// and https:// support option pubKeyPinsSha256 with a comma-separated list of base64 public key hashes like HPKP, not supplying this causes TLS connections to be unauthenticated (vulnerable to MITM)
+# https:// also validates the hostname for now like a browser would
+# default 'https://dns.google.com/experimental?ct#name=dns.google.com'
+resolvers=\
+ tls://89.233.43.71#name=unicast.censurfridns.dk;pubKeyPinsSha256=wikE3jYAA6jQmXYTr/rbHeEPmC78dQwZbQp6WdrseEs= \
+ tls://145.100.185.15#name=dnsovertls.sinodun.com;pubKeyPinsSha256=62lKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL4= \
+ tls://145.100.185.16#name=dnsovertls1.sinodun.com;pubKeyPinsSha256=cE2ecALeE5B+urJhDrJlVFmf38cJLAvqekONvjvpqUA= \
+ tls://185.49.141.37#name=getdnsapi.net;pubKeyPinsSha256=foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9Q= \
+ https://dns.google.com/experimental?ct#name=dns.google.com
+#resolvers=https://dns.google.com/experimental?ct
+#resolvers=tcp://8.8.4.4:53
+#resolvers=tls://89.233.43.71:853#pubKeyPinsSha256=wikE3jYAA6jQmXYTr/rbHeEPmC78dQwZbQp6WdrseEs=
+
+# below here are resolver options that may be defined here and/or at the resolver level, if both resolver level wins
+
+# proxy: defines a proxy to use for all connections to this resolver, supports socks:// and http://, default none
+#proxy=socks://127.0.0.1:9050
+
+# pubKeyPinsSha256: should be on an individual resolver level, specify comma-seperated base64 public key hashes like HPKP, not supplying this causes TLS connections to be unauthenticated (vulnerable to MITM), default none
+# https:// also validates the hostname for now like a browser would
+#pubKeyPinsSha256=wikE3jYAA6jQmXYTr/rbHeEPmC78dQwZbQp6WdrseEs=
+
+# maxRetries: maximum number of times a request is re-queued to be resolved upstream due to failure before giving up, this is maximum retries total, not per-resolver, default resolvers.length * 2
+#maxRetries=5
+
+# name: human-readable name of resolver, might end up in logs, default full resolver URI
+#name=somename
+
+# connectTimeout: TCP connection timeout in milliseconds to upstream resolver, default 500
+connectTimeout=500
diff --git a/xmpp-dox/jdnsproxy.xmpp.resolver.properties b/xmpp-dox/jdnsproxy.xmpp.resolver.properties
new file mode 100644
index 0000000..c1d6e5d
--- /dev/null
+++ b/xmpp-dox/jdnsproxy.xmpp.resolver.properties
@@ -0,0 +1,48 @@
+# minTtl: rewrite TTLs lower than this to this value, default 600, 0 disables this feature
+minTtl=600
+
+# staleResponseTimeout: milliseconds to wait for response to query before serving a stale record if we have it, default 1000
+staleResponseTimeout=1000
+# staleResponseTtl: TTL to apply to stale record when above timeout is met and stale record is served, default 10
+staleResponseTtl=10
+
+# cacheFile: path to file to persist cache to at an interval
+cacheFile=dnscache.map
+# cacheDelayMinutes: how often to write the cache to disk
+cacheDelayMinutes=60
+
+# packetQueueLength: maximum requests queued waiting for responses from upstream, all resolvers specified process from this queue, cached responses don't enter this queue, default 100, 0 means unlimited
+packetQueueLength=100
+
+# listeners: list of listeners, currently supports tcp:// and udp:// with no options, default 'tcp://127.0.0.1:5353 udp://127.0.0.1:5353'
+listeners=tcp://127.0.0.1:5353 udp://127.0.0.1:5353
+
+# resolvers: list of resolvers with or without options, whitespace separated, options are in fragment separated by ;
+# currently support tcp:// (regular DNS-over-TCP), tls:// (DNS-over-TLS), http:// https:// (DNS-over-HTTPS)
+# both tls:// and https:// support option pubKeyPinsSha256 with a comma-separated list of base64 public key hashes like HPKP, not supplying this causes TLS connections to be unauthenticated (vulnerable to MITM)
+# https:// also validates the hostname for now like a browser would
+# default 'https://dns.google.com/experimental?ct#name=dns.google.com'
+# also supports xmpp:// (DNS-over-XMPP), put IP:port of XMPP server, along with username/password to login with, and a resolverJid
+resolvers=\
+ xmpp://208.68.163.210:5222#user=anyjid@example.org/resolver;pass=y0urPa55W0rDHere;resolverJid=dns@moparisthebest.com/listener
+#resolvers=https://dns.google.com/experimental?ct
+#resolvers=tcp://8.8.4.4:53
+#resolvers=tls://89.233.43.71:853#pubKeyPinsSha256=wikE3jYAA6jQmXYTr/rbHeEPmC78dQwZbQp6WdrseEs=
+
+# below here are resolver options that may be defined here and/or at the resolver level, if both resolver level wins
+
+# proxy: defines a proxy to use for all connections to this resolver, supports socks:// and http://, default none
+#proxy=socks://127.0.0.1:9050
+
+# pubKeyPinsSha256: should be on an individual resolver level, specify comma-seperated base64 public key hashes like HPKP, not supplying this causes TLS connections to be unauthenticated (vulnerable to MITM), default none
+# https:// also validates the hostname for now like a browser would
+#pubKeyPinsSha256=wikE3jYAA6jQmXYTr/rbHeEPmC78dQwZbQp6WdrseEs=
+
+# maxRetries: maximum number of times a request is re-queued to be resolved upstream due to failure before giving up, this is maximum retries total, not per-resolver, default resolvers.length * 2
+#maxRetries=5
+
+# name: human-readable name of resolver, might end up in logs, default full resolver URI
+#name=somename
+
+# connectTimeout: TCP connection timeout in milliseconds to upstream resolver, default 500
+connectTimeout=500
diff --git a/xmpp-dox/pom.xml b/xmpp-dox/pom.xml
new file mode 100644
index 0000000..24b71e2
--- /dev/null
+++ b/xmpp-dox/pom.xml
@@ -0,0 +1,34 @@
+
+
+
+ com.moparisthebest.dns
+ jDnsProxy-parent
+ 1.0-SNAPSHOT
+
+ 4.0.0
+ xmpp-dox
+ jar
+ 1.0-SNAPSHOT
+ ${project.artifactId}
+
+
+ ${project.groupId}
+ jDnsProxy
+ ${project.version}
+ provided
+
+
+ org.igniterealtime.smack
+ smack-tcp
+ 4.3.2
+
+
+ org.igniterealtime.smack
+ smack-java7
+ 4.3.2
+
+
+
+ ${project.artifactId}
+
+
diff --git a/xmpp-dox/src/main/java/com/moparisthebest/dns/listen/XmppListener.java b/xmpp-dox/src/main/java/com/moparisthebest/dns/listen/XmppListener.java
new file mode 100644
index 0000000..ada9bf7
--- /dev/null
+++ b/xmpp-dox/src/main/java/com/moparisthebest/dns/listen/XmppListener.java
@@ -0,0 +1,153 @@
+package com.moparisthebest.dns.listen;
+
+import com.moparisthebest.dns.dto.Packet;
+import com.moparisthebest.dns.net.ParsedUrl;
+import com.moparisthebest.dns.resolve.BaseRequestResponse;
+import com.moparisthebest.dns.resolve.Resolver;
+import com.moparisthebest.dns.xmpp.ConnectionDetails;
+import com.moparisthebest.dns.xmpp.DnsIq;
+import org.jivesoftware.smack.AbstractXMPPConnection;
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.iqrequest.IQRequestHandler;
+import org.jivesoftware.smack.packet.*;
+import org.jxmpp.jid.Jid;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+
+public class XmppListener implements Listener {
+
+ private final ConnectionDetails cd;
+
+ private final Resolver resolver;
+ private final ExecutorService executor;
+
+ private boolean running = true;
+ private Thread thisThread = null;
+
+ public XmppListener(final ParsedUrl parsedUrl, final Resolver resolver, final ExecutorService executor) {
+ this.cd = new ConnectionDetails(parsedUrl);
+ this.resolver = resolver;
+ this.executor = executor;
+ }
+
+ @Override
+ public void run() {
+ while (running)
+ try {
+ final AbstractXMPPConnection connection = cd.login();
+
+ thisThread = Thread.currentThread();
+
+ connection.registerIQRequestHandler(new IQRequestHandler() {
+ @Override
+ public IQ handleIQRequest(final IQ req) {
+ //System.out.println("request: " + req);
+ //System.out.println("request XML: " + req.toXML(StreamOpen.CLIENT_NAMESPACE));
+
+ final byte[] request = DnsIq.parseDnsIq(req);
+ if (request != null) {
+ //System.out.println("good request: " + req);
+ final XmppRequestResponse requestResponse = new XmppRequestResponse(req.getFrom(), new Packet(request));
+
+ resolver.resolveAsync(requestResponse).whenCompleteAsync((urr, t) -> {
+ if (t != null) {
+ t.printStackTrace();
+ return;
+ }
+ //debugPacket(urr.getResponse().getBuf());
+
+ final IQ resp = DnsIq.responseFor(req, urr.getResponse().getBuf());
+
+ try {
+ //System.out.println("dns response: " + resp.toString());
+ //System.out.println("dns response XML: " + resp.toXML(StreamOpen.CLIENT_NAMESPACE));
+ connection.sendStanza(resp);
+ } catch (SmackException.NotConnectedException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ }, executor);
+ }
+
+ // todo: respond with error Iq?
+ return null;
+ }
+
+ @Override
+ public Mode getMode() {
+ return Mode.sync;
+ }
+
+ @Override
+ public IQ.Type getType() {
+ return IQ.Type.get;
+ }
+
+ @Override
+ public String getElement() {
+ return DnsIq.ELEMENT;
+ }
+
+ @Override
+ public String getNamespace() {
+ return DnsIq.NAMESPACE;
+ }
+ });
+
+ while (running)
+ Thread.sleep(Long.MAX_VALUE);
+
+ } catch (IOException | XMPPException | SmackException e) {
+ e.printStackTrace();
+ try {
+ // let's not burn the CPU
+ Thread.sleep(1000);
+ } catch (InterruptedException e1) {
+ //ignore
+ }
+ } catch (InterruptedException e) {
+ if (running) {
+ // not good
+ e.printStackTrace();
+ try {
+ // let's not burn the CPU
+ Thread.sleep(1000);
+ } catch (InterruptedException e1) {
+ //ignore
+ }
+ } else {
+ // being shutdown
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ running = false;
+ if (thisThread != null)
+ thisThread.interrupt();
+ }
+
+ public class XmppRequestResponse extends BaseRequestResponse {
+
+ private final Jid requester;
+
+ public XmppRequestResponse(final Jid requester, final Packet request) {
+ super(request);
+ this.requester = requester;
+ }
+
+ public Jid getRequester() {
+ return requester;
+ }
+
+ @Override
+ public String toString() {
+ return "XmppRequestResponse{" +
+ "requester=" + requester +
+ "} " + super.toString();
+ }
+ }
+}
diff --git a/xmpp-dox/src/main/java/com/moparisthebest/dns/listen/XmppServices.java b/xmpp-dox/src/main/java/com/moparisthebest/dns/listen/XmppServices.java
new file mode 100644
index 0000000..8272e31
--- /dev/null
+++ b/xmpp-dox/src/main/java/com/moparisthebest/dns/listen/XmppServices.java
@@ -0,0 +1,16 @@
+package com.moparisthebest.dns.listen;
+
+import com.moparisthebest.dns.net.ParsedUrl;
+import com.moparisthebest.dns.resolve.Resolver;
+
+import java.util.concurrent.ExecutorService;
+
+public class XmppServices implements Services {
+ @Override
+ public Listener getListener(ParsedUrl parsedUrl, final Resolver resolver, final ExecutorService executor) {
+ if ("xmpp".equals(parsedUrl.getProtocol())) {
+ return new XmppListener(parsedUrl, resolver, executor);
+ }
+ return null;
+ }
+}
diff --git a/xmpp-dox/src/main/java/com/moparisthebest/dns/resolve/XmppResolver.java b/xmpp-dox/src/main/java/com/moparisthebest/dns/resolve/XmppResolver.java
new file mode 100644
index 0000000..d7729e0
--- /dev/null
+++ b/xmpp-dox/src/main/java/com/moparisthebest/dns/resolve/XmppResolver.java
@@ -0,0 +1,43 @@
+package com.moparisthebest.dns.resolve;
+
+import com.moparisthebest.dns.dto.Packet;
+import com.moparisthebest.dns.net.ParsedUrl;
+import com.moparisthebest.dns.xmpp.ConnectionDetails;
+import com.moparisthebest.dns.xmpp.DnsIq;
+import org.jivesoftware.smack.AbstractXMPPConnection;
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.IQ;
+import org.jxmpp.jid.Jid;
+import org.jxmpp.jid.impl.JidCreate;
+
+import java.io.*;
+
+public class XmppResolver implements Resolver {
+
+ private final Jid to;
+ private final AbstractXMPPConnection connection;
+
+ public XmppResolver(final ParsedUrl parsedUrl) {
+ try {
+ to = JidCreate.from(parsedUrl.getProps().get("resolverJid"));
+ this.connection = new ConnectionDetails(parsedUrl).login();
+ } catch (InterruptedException | XMPPException | SmackException | IOException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Packet resolve(final Packet request) throws Exception {
+ // todo: should do this async
+ final IQ req = DnsIq.requestTo(to, request.getBuf());
+ //System.out.println("dns request: " + req.toString());
+ //System.out.println("dns request XML: " + req.toXML(StreamOpen.CLIENT_NAMESPACE));
+ final IQ resp = connection.sendIqRequestAndWaitForResponse(req);
+ final byte[] ret = DnsIq.parseDnsIq(resp);
+ if(ret == null)
+ throw new Exception("XMPP request failed");
+ return new Packet(ret);
+ }
+}
diff --git a/xmpp-dox/src/main/java/com/moparisthebest/dns/resolve/XmppServices.java b/xmpp-dox/src/main/java/com/moparisthebest/dns/resolve/XmppServices.java
new file mode 100644
index 0000000..b84ff07
--- /dev/null
+++ b/xmpp-dox/src/main/java/com/moparisthebest/dns/resolve/XmppServices.java
@@ -0,0 +1,13 @@
+package com.moparisthebest.dns.resolve;
+
+import com.moparisthebest.dns.net.ParsedUrl;
+
+public class XmppServices implements Services {
+ @Override
+ public Resolver getResolver(ParsedUrl parsedUrl) {
+ if ("xmpp".equals(parsedUrl.getProtocol())) {
+ return new XmppResolver(parsedUrl);
+ }
+ return null;
+ }
+}
diff --git a/xmpp-dox/src/main/java/com/moparisthebest/dns/xmpp/ConnectionDetails.java b/xmpp-dox/src/main/java/com/moparisthebest/dns/xmpp/ConnectionDetails.java
new file mode 100644
index 0000000..0493e17
--- /dev/null
+++ b/xmpp-dox/src/main/java/com/moparisthebest/dns/xmpp/ConnectionDetails.java
@@ -0,0 +1,57 @@
+package com.moparisthebest.dns.xmpp;
+
+import com.moparisthebest.dns.net.ParsedUrl;
+import org.jivesoftware.smack.AbstractXMPPConnection;
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.tcp.XMPPTCPConnection;
+import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
+import org.jxmpp.util.XmppStringUtils;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Objects;
+
+public class ConnectionDetails {
+
+ private final String jid;
+ private final String password;
+ private final InetSocketAddress isa;
+
+ private final long replyTimeout;
+
+ public ConnectionDetails(final String jid, final String password, final InetSocketAddress isa, final long replyTimeout) {
+ Objects.requireNonNull(jid, "user must be non-null");
+ Objects.requireNonNull(isa, "isa must be non-null");
+ this.jid = jid;
+ this.password = password;
+ this.isa = isa;
+ this.replyTimeout = replyTimeout;
+ }
+
+ public ConnectionDetails(final ParsedUrl parsedUrl) {
+ this(parsedUrl.getProps().get("user"), parsedUrl.getProps().get("pass"),
+ (InetSocketAddress) parsedUrl.getAddr(),
+ Long.parseLong(parsedUrl.getProps().getOrDefault("replyTimeout", "5000")));
+ }
+
+ public AbstractXMPPConnection login() throws InterruptedException, XMPPException, SmackException, IOException {
+
+ final XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder()
+ .setUsernameAndPassword(XmppStringUtils.parseLocalpart(jid), password)
+ .setXmppDomain(XmppStringUtils.parseDomain(jid))
+ .setHostAddress(isa.getAddress())
+ .setPort(isa.getPort());
+
+ final String resource = XmppStringUtils.parseResource(jid);
+ if(resource != null && !resource.isEmpty()) {
+ builder.setResource(resource);
+ }
+
+ final AbstractXMPPConnection connection = new XMPPTCPConnection(builder.build());
+ connection.setReplyTimeout(replyTimeout);
+ connection.connect().login();
+
+ return connection;
+ }
+}
diff --git a/xmpp-dox/src/main/java/com/moparisthebest/dns/xmpp/DnsIq.java b/xmpp-dox/src/main/java/com/moparisthebest/dns/xmpp/DnsIq.java
new file mode 100644
index 0000000..310003d
--- /dev/null
+++ b/xmpp-dox/src/main/java/com/moparisthebest/dns/xmpp/DnsIq.java
@@ -0,0 +1,56 @@
+package com.moparisthebest.dns.xmpp;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.UnparsedIQ;
+import org.jxmpp.jid.Jid;
+
+import java.nio.ByteBuffer;
+import java.util.Base64;
+
+public class DnsIq extends IQ {
+
+ private static final Base64.Decoder decoder = Base64.getDecoder();
+ private static final Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
+
+ public static final String ELEMENT = "dns";
+ public static final String NAMESPACE = "urn:xmpp:dox:0";
+
+ private final ByteBuffer bb;
+
+ private DnsIq(final ByteBuffer bb) {
+ super(ELEMENT, NAMESPACE);
+ this.bb = bb;
+ }
+
+ public static DnsIq responseFor(final IQ iq, final ByteBuffer bb) {
+ final DnsIq ret = new DnsIq(bb);
+ ret.setStanzaId(iq.getStanzaId());
+ ret.setTo(iq.getFrom());
+ ret.setType(Type.result);
+ return ret;
+ }
+
+ public static DnsIq requestTo(final Jid to, final ByteBuffer bb) {
+ final DnsIq ret = new DnsIq(bb);
+ ret.setTo(to);
+ ret.setType(Type.get);
+ return ret;
+ }
+
+ public static byte[] parseDnsIq(final IQ iq) {
+ // todo: yikes this whole method is awful, what happened to XML ? investigate this later
+ if(!(iq instanceof UnparsedIQ))
+ return null;
+ final UnparsedIQ uiq = (UnparsedIQ) iq;
+ final String actualRequest = uiq.getContent().toString().replaceAll("<[^>]+>", "");
+ //System.out.println("actualRequest: " + actualRequest);
+ return decoder.decode(actualRequest);
+ }
+
+ @Override
+ protected IQChildElementXmlStringBuilder getIQChildElementBuilder(final IQChildElementXmlStringBuilder buf) {
+ buf.rightAngleBracket();
+ buf.append(encoder.encodeToString(bb.array()));
+ return buf;
+ }
+}
diff --git a/xmpp-dox/src/main/resources/META-INF/services/com.moparisthebest.dns.listen.Services b/xmpp-dox/src/main/resources/META-INF/services/com.moparisthebest.dns.listen.Services
new file mode 100644
index 0000000..9f0754a
--- /dev/null
+++ b/xmpp-dox/src/main/resources/META-INF/services/com.moparisthebest.dns.listen.Services
@@ -0,0 +1 @@
+com.moparisthebest.dns.listen.XmppServices
\ No newline at end of file
diff --git a/xmpp-dox/src/main/resources/META-INF/services/com.moparisthebest.dns.resolve.Services b/xmpp-dox/src/main/resources/META-INF/services/com.moparisthebest.dns.resolve.Services
new file mode 100644
index 0000000..2514dcc
--- /dev/null
+++ b/xmpp-dox/src/main/resources/META-INF/services/com.moparisthebest.dns.resolve.Services
@@ -0,0 +1 @@
+com.moparisthebest.dns.resolve.XmppServices
\ No newline at end of file