1package eu.siacs.conversations.utils;
2
3import android.os.Bundle;
4import android.util.Log;
5
6import java.io.IOException;
7import java.net.InetAddress;
8import java.net.SocketTimeoutException;
9import java.util.ArrayList;
10import java.util.Collections;
11import java.util.Random;
12import java.util.TreeMap;
13import java.util.regex.Pattern;
14
15import de.measite.minidns.Client;
16import de.measite.minidns.DNSMessage;
17import de.measite.minidns.Record;
18import de.measite.minidns.Record.CLASS;
19import de.measite.minidns.Record.TYPE;
20import de.measite.minidns.record.A;
21import de.measite.minidns.record.AAAA;
22import de.measite.minidns.record.Data;
23import de.measite.minidns.record.SRV;
24import de.measite.minidns.util.NameUtil;
25import eu.siacs.conversations.Config;
26import eu.siacs.conversations.xmpp.jid.Jid;
27
28public class DNSHelper {
29
30 public static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
31 public static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
32 public static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
33 public static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
34 public static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
35
36 protected static Client client = new Client();
37
38 public static Bundle getSRVRecord(final Jid jid) throws IOException {
39 final String host = jid.getDomainpart();
40 String dns[] = client.findDNS();
41 for (int i = 0; i < dns.length; ++i) {
42 InetAddress ip = InetAddress.getByName(dns[i]);
43 Bundle b = queryDNS(host, ip);
44 if (b.containsKey("values") || i == dns.length - 1) {
45 return b;
46 }
47 }
48 return null;
49 }
50
51 public static Bundle queryDNS(String host, InetAddress dnsServer) {
52 Bundle bundle = new Bundle();
53 try {
54 String qname = "_xmpp-client._tcp." + host;
55 Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host);
56 DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress());
57
58 TreeMap<Integer, ArrayList<SRV>> priorities = new TreeMap<>();
59 TreeMap<String, ArrayList<String>> ips4 = new TreeMap<>();
60 TreeMap<String, ArrayList<String>> ips6 = new TreeMap<>();
61
62 for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) {
63 for (Record rr : rrset) {
64 Data d = rr.getPayload();
65 if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) {
66 SRV srv = (SRV) d;
67 if (!priorities.containsKey(srv.getPriority())) {
68 priorities.put(srv.getPriority(),new ArrayList<SRV>());
69 }
70 priorities.get(srv.getPriority()).add(srv);
71 }
72 if (d instanceof A) {
73 A a = (A) d;
74 if (!ips4.containsKey(rr.getName())) {
75 ips4.put(rr.getName(), new ArrayList<String>());
76 }
77 ips4.get(rr.getName()).add(a.toString());
78 }
79 if (d instanceof AAAA) {
80 AAAA aaaa = (AAAA) d;
81 if (!ips6.containsKey(rr.getName())) {
82 ips6.put(rr.getName(), new ArrayList<String>());
83 }
84 ips6.get(rr.getName()).add("[" + aaaa.toString() + "]");
85 }
86 }
87 }
88
89 ArrayList<SRV> result = new ArrayList<>();
90 for (ArrayList<SRV> s : priorities.values()) {
91 result.addAll(s);
92 }
93
94 ArrayList<Bundle> values = new ArrayList<>();
95 if (result.size() == 0) {
96 DNSMessage response;
97 response = client.query(host, TYPE.A, CLASS.IN, dnsServer.getHostAddress());
98 for(int i = 0; i < response.getAnswers().length; ++i) {
99 values.add(createNamePortBundle(host,5222,response.getAnswers()[i].getPayload()));
100 }
101 response = client.query(host, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
102 for(int i = 0; i < response.getAnswers().length; ++i) {
103 values.add(createNamePortBundle(host,5222,response.getAnswers()[i].getPayload()));
104 }
105 values.add(createNamePortBundle(host,5222));
106 bundle.putParcelableArrayList("values", values);
107 return bundle;
108 }
109 for (SRV srv : result) {
110 if (ips6.containsKey(srv.getName())) {
111 values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6));
112 } else {
113 DNSMessage response = client.query(srv.getName(), TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
114 for(int i = 0; i < response.getAnswers().length; ++i) {
115 values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload()));
116 }
117 }
118 if (ips4.containsKey(srv.getName())) {
119 values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4));
120 } else {
121 DNSMessage response = client.query(srv.getName(), TYPE.A, CLASS.IN, dnsServer.getHostAddress());
122 for(int i = 0; i < response.getAnswers().length; ++i) {
123 values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload()));
124 }
125 }
126 values.add(createNamePortBundle(srv.getName(), srv.getPort()));
127 }
128 bundle.putParcelableArrayList("values", values);
129 } catch (SocketTimeoutException e) {
130 bundle.putString("error", "timeout");
131 } catch (Exception e) {
132 e.printStackTrace();
133 bundle.putString("error", "unhandled");
134 }
135 return bundle;
136 }
137
138 private static Bundle createNamePortBundle(String name, int port) {
139 Bundle namePort = new Bundle();
140 namePort.putString("name", name);
141 namePort.putInt("port", port);
142 return namePort;
143 }
144
145 private static Bundle createNamePortBundle(String name, int port, TreeMap<String, ArrayList<String>> ips) {
146 Bundle namePort = new Bundle();
147 namePort.putString("name", name);
148 namePort.putInt("port", port);
149 if (ips!=null) {
150 ArrayList<String> ip = ips.get(name);
151 Collections.shuffle(ip, new Random());
152 namePort.putString("ip", ip.get(0));
153 }
154 return namePort;
155 }
156
157 private static Bundle createNamePortBundle(String name, int port, Data data) {
158 Bundle namePort = new Bundle();
159 namePort.putString("name", name);
160 namePort.putInt("port", port);
161 if (data instanceof A) {
162 namePort.putString("ip", data.toString());
163 } else if (data instanceof AAAA) {
164 namePort.putString("ip","["+data.toString()+"]");
165 }
166 return namePort;
167 }
168
169 public static boolean isIp(final String server) {
170 return PATTERN_IPV4.matcher(server).matches()
171 || PATTERN_IPV6.matcher(server).matches()
172 || PATTERN_IPV6_6HEX4DEC.matcher(server).matches()
173 || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches()
174 || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches();
175 }
176}