1package eu.siacs.conversations.utils;
2
3import de.measite.minidns.Client;
4import de.measite.minidns.DNSMessage;
5import de.measite.minidns.Record;
6import de.measite.minidns.Record.TYPE;
7import de.measite.minidns.Record.CLASS;
8import de.measite.minidns.record.SRV;
9import de.measite.minidns.record.Data;
10
11import java.io.IOException;
12import java.net.InetAddress;
13import java.net.SocketTimeoutException;
14import java.util.ArrayList;
15import java.util.Collections;
16import java.util.Random;
17import java.util.TreeMap;
18
19import android.os.Bundle;
20import android.util.Log;
21
22public class DNSHelper {
23 protected static Client client = new Client();
24
25 public static Bundle getSRVRecord(String host) throws IOException {
26 String dns[] = client.findDNS();
27
28 if (dns != null) {
29 // we have a list of DNS servers, let's go
30 for (String dnsserver : dns) {
31 InetAddress ip = InetAddress.getByName(dnsserver);
32 Bundle b = queryDNS(host, ip);
33 if (b.containsKey("name")) {
34 return b;
35 }
36 }
37 }
38
39 // fallback
40 return queryDNS(host, InetAddress.getByName("8.8.8.8"));
41 }
42
43 public static Bundle queryDNS(String host, InetAddress dnsServer) {
44 Bundle namePort = new Bundle();
45 try {
46 Log.d("xmppService", "using dns server: " + dnsServer.getHostAddress()
47 + " to look up " + host);
48 DNSMessage message =
49 client.query(
50 "_xmpp-client._tcp." + host,
51 TYPE.SRV,
52 CLASS.IN,
53 dnsServer.getHostAddress());
54
55 // How should we handle priorities and weight?
56 // Wikipedia has a nice article about priorities vs. weights:
57 // https://en.wikipedia.org/wiki/SRV_record#Provisioning_for_high_service_availability
58
59 // we bucket the SRV records based on priority, pick per priority
60 // a random order respecting the weight, and dump that priority by
61 // priority
62
63 TreeMap<Integer, ArrayList<SRV>> priorities =
64 new TreeMap<Integer, ArrayList<SRV>>();
65
66 for (Record rr : message.getAnswers()) {
67 Data d = rr.getPayload();
68 if (d instanceof SRV) {
69 SRV srv = (SRV) d;
70 if (!priorities.containsKey(srv.getPriority())) {
71 priorities.put(srv.getPriority(), new ArrayList<SRV>(2));
72 }
73 priorities.get(srv.getPriority()).add(srv);
74 }
75 }
76
77 Random rnd = new Random();
78 ArrayList<SRV> result = new ArrayList<SRV>(priorities.size() * 2 + 1);
79 for (ArrayList<SRV> s: priorities.values()) {
80
81 // trivial case
82 if (s.size() <= 1) {
83 result.addAll(s);
84 continue;
85 }
86
87 long totalweight = 0l;
88 for (SRV srv: s) {
89 totalweight += srv.getWeight();
90 }
91
92 while (totalweight > 0l && s.size() > 0) {
93 long p = (rnd.nextLong() & 0x7fffffffffffffffl) % totalweight;
94 int i = 0;
95 while (p > 0) {
96 p -= s.get(i++).getPriority();
97 }
98 i--;
99 // remove is expensive, but we have only a few entries anyway
100 SRV srv = s.remove(i);
101 totalweight -= srv.getWeight();
102 result.add(srv);
103 }
104
105 Collections.shuffle(s, rnd);
106 result.addAll(s);
107
108 }
109
110 if (result.size() == 0) {
111 namePort.putString("error", "nosrv");
112 return namePort;
113 }
114 // we now have a list of servers to try :-)
115
116 // classic name/port pair
117 namePort.putString("name", result.get(0).getName());
118 namePort.putInt("port", result.get(0).getPort());
119
120 // add all other records
121 int i = 0;
122 for (SRV srv : result) {
123 namePort.putString("name" + i, srv.getName());
124 namePort.putInt("port" + i, srv.getPort());
125 i++;
126 }
127
128 } catch (SocketTimeoutException e) {
129 Log.d("xmppService", "timeout during dns");
130 namePort.putString("error", "timeout");
131 } catch (Exception e) {
132 Log.d("xmppService","unhandled exception in sub project");
133 namePort.putString("error", "unhandled");
134 }
135 return namePort;
136 }
137
138 final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
139
140 public static String bytesToHex(byte[] bytes) {
141 char[] hexChars = new char[bytes.length * 2];
142 for (int j = 0; j < bytes.length; j++) {
143 int v = bytes[j] & 0xFF;
144 hexChars[j * 2] = hexArray[v >>> 4];
145 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
146 }
147 return new String(hexChars);
148 }
149}