diff --git a/.gitmodules b/.gitmodules index 860d9662..886fc27b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "libs/openpgp-keychain"] path = libs/openpgp-keychain url = https://github.com/openpgp-keychain/openpgp-keychain.git +[submodule "libs/minidns"] + path = libs/minidns + url = https://github.com/rtreffer/minidns.git + diff --git a/libs/minidns b/libs/minidns new file mode 160000 index 00000000..d5cca3b3 --- /dev/null +++ b/libs/minidns @@ -0,0 +1 @@ +Subproject commit d5cca3b3a48cfaed3008050fcab9574d97d771cc diff --git a/project.properties b/project.properties index 1a482f6f..c6492266 100644 --- a/project.properties +++ b/project.properties @@ -13,3 +13,4 @@ # Project target. target=android-19 android.library.reference.1=libs/openpgp-keychain/OpenPGP-Keychain-API/libraries/openpgp-api-library +android.library.reference.2=libs/minidns diff --git a/src/eu/siacs/conversations/utils/DNSHelper.java b/src/eu/siacs/conversations/utils/DNSHelper.java index 3197acb1..c5a3eeb0 100644 --- a/src/eu/siacs/conversations/utils/DNSHelper.java +++ b/src/eu/siacs/conversations/utils/DNSHelper.java @@ -1,125 +1,134 @@ package eu.siacs.conversations.utils; -import java.io.ByteArrayOutputStream; +import de.measite.minidns.Client; +import de.measite.minidns.DNSMessage; +import de.measite.minidns.Record; +import de.measite.minidns.Record.TYPE; +import de.measite.minidns.Record.CLASS; +import de.measite.minidns.record.SRV; +import de.measite.minidns.record.Data; + import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.DatagramPacket; -import java.net.DatagramSocket; import java.net.InetAddress; import java.util.ArrayList; +import java.util.Collections; import java.util.Random; +import java.util.TreeMap; import android.os.Bundle; import android.util.Log; public class DNSHelper { - public static Bundle getSRVRecord(String host) throws IOException { - InetAddress ip = InetAddress.getByName("8.8.8.8"); - try { - Class SystemProperties = Class - .forName("android.os.SystemProperties"); - Method method = SystemProperties.getMethod("get", - new Class[] { String.class }); - ArrayList servers = new ArrayList(); - for (String name : new String[] { "net.dns1", "net.dns2", - "net.dns3", "net.dns4", }) { - String value = (String) method.invoke(null, name); + protected static Client client = new Client(); - if (value != null && !"".equals(value) - && !servers.contains(value)) { - ip = InetAddress.getByName(value); - servers.add(value); - Bundle result = queryDNS(host, ip); - if (!result.containsKey("error")||("nosrv".equals(result.getString("error")))) { - return result; - } + public static Bundle getSRVRecord(String host) throws IOException { + String dns[] = client.findDNS(); + + if (dns != null) { + // we have a list of DNS servers, let's go + for (String dnsserver : dns) { + InetAddress ip = InetAddress.getByName(dnsserver); + Bundle b = queryDNS(host, ip); + if (b.containsKey("name")) { + return b; } } - } catch (Exception e) { - Log.d("xmppService","error during system calls"); } - ip = InetAddress.getByName("8.8.8.8"); - return queryDNS(host, ip); + + // fallback + return queryDNS(host, InetAddress.getByName("8.8.8.8")); } public static Bundle queryDNS(String host, InetAddress dnsServer) { Bundle namePort = new Bundle(); try { - Log.d("xmppService", "using dns server: " + dnsServer.toString() + Log.d("xmppService", "using dns server: " + dnsServer.getHostAddress() + " to look up " + host); - String[] hostParts = host.split("\\."); - byte[] transId = new byte[2]; - Random random = new Random(); - random.nextBytes(transId); - byte[] header = { 0x01, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x0c, 0x5f, 0x78, 0x6d, 0x70, 0x70, 0x2d, 0x63, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x04, 0x5f, 0x74, 0x63, 0x70 }; - byte[] rest = { 0x00, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x29, - 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - ByteArrayOutputStream output = new ByteArrayOutputStream(); - output.write(transId); - output.write(header); - for (int i = 0; i < hostParts.length; ++i) { - char[] tmpChars = hostParts[i].toCharArray(); - byte[] tmp = new byte[tmpChars.length]; - for (int j = 0; j < tmpChars.length; ++j) { - tmp[j] = (byte) tmpChars[j]; - } - output.write(tmp.length); - output.write(tmp); - } - output.write(rest); - byte[] sendPaket = output.toByteArray(); - int realLenght = sendPaket.length - 11; - DatagramPacket packet = new DatagramPacket(sendPaket, - sendPaket.length, dnsServer, 53); - DatagramSocket datagramSocket = new DatagramSocket(); - datagramSocket.send(packet); - byte[] receiveData = new byte[1024]; + DNSMessage message = + client.query( + "_xmpp-client._tcp." + host, + TYPE.SRV, + CLASS.IN, + dnsServer.getHostAddress()); - DatagramPacket receivePacket = new DatagramPacket(receiveData, - receiveData.length); - datagramSocket.setSoTimeout(7000); //die sieben ist meine zahl - datagramSocket.receive(receivePacket); - if (receiveData[3] != -128) { - namePort.putString("error", "nosrv"); - return namePort; - } - namePort.putInt( - "port", - calcPort(receiveData[realLenght + 16], - receiveData[realLenght + 17])); - int i = realLenght + 18; - int wordLenght = 0; - StringBuilder builder = new StringBuilder(); - while (receiveData[i] != 0) { - if (wordLenght > 0) { - builder.append((char) receiveData[i]); - --wordLenght; - } else { - wordLenght = receiveData[i]; - builder.append("."); + // How should we handle priorities and weight? + // Wikipedia has a nice article about priorities vs. weights: + // https://en.wikipedia.org/wiki/SRV_record#Provisioning_for_high_service_availability + + // we bucket the SRV records based on priority, pick per priority + // a random order respecting the weight, and dump that priority by + // priority + + TreeMap> priorities = + new TreeMap>(); + + for (Record rr : message.getAnswers()) { + Data d = rr.getPayload(); + if (d instanceof SRV) { + SRV srv = (SRV) d; + if (!priorities.containsKey(srv.getPriority())) { + priorities.put(srv.getPriority(), new ArrayList(2)); + } + priorities.get(srv.getPriority()).add(srv); } - ++i; } - builder.replace(0, 1, ""); - byte type = receiveData[i + 1]; - byte type2 = receiveData[i + 2]; - if ((type == -64) || (type == type2)) { - namePort.putString("name", builder.toString()); - return namePort; - } else { - Log.d("xmppService", "type=" + type + " type2=" + type2 + " " - + builder.toString()); + + Random rnd = new Random(); + ArrayList result = new ArrayList(priorities.size() * 2 + 1); + for (ArrayList s: priorities.values()) { + + // trivial case + if (s.size() <= 1) { + result.addAll(s); + continue; + } + + long totalweight = 0l; + for (SRV srv: s) { + totalweight += srv.getWeight(); + } + + while (totalweight > 0l && s.size() > 0) { + long p = (rnd.nextLong() & 0x7fffffffffffffffl) % totalweight; + int i = 0; + while (p > 0) { + p -= s.get(i++).getPriority(); + } + i--; + // remove is expensive, but we have only a few entries anyway + SRV srv = s.remove(i); + totalweight -= srv.getWeight(); + result.add(srv); + } + + Collections.shuffle(s, rnd); + result.addAll(s); + + } + + if (result.size() == 0) { namePort.putString("error", "nosrv"); return namePort; } + // we now have a list of servers to try :-) + + // classic name/port pair + namePort.putString("name", result.get(0).getName()); + namePort.putInt("port", result.get(0).getPort()); + + // add all other records + int i = 0; + for (SRV srv : result) { + namePort.putString("name" + i, srv.getName()); + namePort.putInt("port" + i, srv.getPort()); + i++; + } + } catch (IOException e) { - Log.d("xmppService", "io execpiton during dns"); + Log.e("xmppService", "io execpiton during dns", e); namePort.putString("error", "timeout"); - return namePort; } + return namePort; } static int calcPort(byte hb, byte lb) {