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