1package de.measite.minidns;
2
3import java.io.IOException;
4import java.io.InputStream;
5import java.io.InputStreamReader;
6import java.io.LineNumberReader;
7import java.lang.reflect.Method;
8import java.net.DatagramPacket;
9import java.net.DatagramSocket;
10import java.net.InetAddress;
11import java.security.NoSuchAlgorithmException;
12import java.security.SecureRandom;
13import java.util.ArrayList;
14import java.util.Arrays;
15import java.util.HashSet;
16import java.util.Random;
17
18import android.util.Log;
19import de.measite.minidns.Record.CLASS;
20import de.measite.minidns.Record.TYPE;
21
22/**
23 * A minimal DNS client for SRV/A/AAAA/NS and CNAME lookups, with IDN support.
24 * This circumvents the missing javax.naming package on android.
25 */
26public class Client {
27
28 /**
29 * The internal random class for sequence generation.
30 */
31 protected Random random;
32
33 /**
34 * Create a new DNS client.
35 */
36 public Client() {
37 try {
38 random = SecureRandom.getInstance("SHA1PRNG");
39 } catch (NoSuchAlgorithmException e1) {
40 random = new SecureRandom();
41 }
42 }
43
44 /**
45 * Query a nameserver for a single entry.
46 * @param name The DNS name to request.
47 * @param type The DNS type to request (SRV, A, AAAA, ...).
48 * @param clazz The class of the request (usually IN for Internet).
49 * @param host The DNS server host.
50 * @return
51 * @throws IOException On IO Errors.
52 */
53 public DNSMessage query(String name, TYPE type, CLASS clazz, String host)
54 throws IOException
55 {
56 Question q = new Question();
57 q.setClazz(clazz);
58 q.setType(type);
59 q.setName(name);
60 return query(q, host);
61 }
62
63 /**
64 * Query the system nameserver for a single entry.
65 * @param name The DNS name to request.
66 * @param type The DNS type to request (SRV, A, AAAA, ...).
67 * @param clazz The class of the request (usually IN for Internet).
68 * @return The DNSMessage reply or null.
69 */
70 public DNSMessage query(String name, TYPE type, CLASS clazz)
71 {
72 Question q = new Question();
73 q.setClazz(clazz);
74 q.setType(type);
75 q.setName(name);
76 return query(q);
77 }
78
79 /**
80 * Query a specific server for one entry.
81 * @param q The question section of the DNS query.
82 * @param host The dns server host.
83 * @throws IOException On IOErrors.
84 */
85 public DNSMessage query(Question q, String host) throws IOException {
86 DNSMessage message = new DNSMessage();
87 message.setQuestions(new Question[]{q});
88 message.setRecursionDesired(true);
89 message.setId(random.nextInt());
90 byte[] buf = message.toArray();
91 DatagramSocket socket = new DatagramSocket();
92 DatagramPacket packet = new DatagramPacket(
93 buf, buf.length, InetAddress.getByName(host), 53);
94 socket.setSoTimeout(5000);
95 socket.send(packet);
96 packet = new DatagramPacket(new byte[513], 513);
97 socket.receive(packet);
98 DNSMessage dnsMessage = DNSMessage.parse(packet.getData());
99 if (dnsMessage.getId() != message.getId()) {
100 return null;
101 }
102 return dnsMessage;
103 }
104
105 /**
106 * Query the system DNS server for one entry.
107 * @param q The question section of the DNS query.
108 */
109 public DNSMessage query(Question q) {
110 String dnsServer[] = findDNS();
111 for (String dns : dnsServer) {
112 try {
113 DNSMessage message = query(q, dns);
114 if (message == null) {
115 continue;
116 }
117 if (message.getResponseCode() !=
118 DNSMessage.RESPONSE_CODE.NO_ERROR) {
119 continue;
120 }
121 for (Record record: message.getAnswers()) {
122 if (record.isAnswer(q)) {
123 return message;
124 }
125 }
126 } catch (IOException ioe) {
127 }
128 }
129 return null;
130 }
131
132 /**
133 * Retrieve a list of currently configured DNS servers.
134 * @return The server array.
135 */
136 public String[] findDNS() {
137 String[] result = findDNSByReflection();
138 if (result != null) {
139 Log.d("minidns/client",
140 "Got DNS servers via reflection: " + Arrays.toString(result));
141 return result;
142 }
143
144 result = findDNSByExec();
145 if (result != null) {
146 Log.d("minidns/client",
147 "Got DNS servers via exec: " + Arrays.toString(result));
148 return result;
149 }
150
151 // fallback for ipv4 and ipv6 connectivity
152 // see https://developers.google.com/speed/public-dns/docs/using
153 Log.d("minidns/client",
154 "No DNS found? Using fallback [8.8.8.8, [2001:4860:4860::8888]]");
155
156 return new String[]{"8.8.8.8", "[2001:4860:4860::8888]"};
157 }
158
159 /**
160 * Try to retrieve the list of dns server by executing getprop.
161 * @return Array of servers, or null on failure.
162 */
163 protected String[] findDNSByExec() {
164 try {
165 Process process = Runtime.getRuntime().exec("getprop");
166 InputStream inputStream = process.getInputStream();
167 LineNumberReader lnr = new LineNumberReader(
168 new InputStreamReader(inputStream));
169 String line = null;
170 HashSet<String> server = new HashSet<String>(6);
171 while ((line = lnr.readLine()) != null) {
172 int split = line.indexOf("]: [");
173 if (split == -1) {
174 continue;
175 }
176 String property = line.substring(1, split);
177 String value = line.substring(split + 4, line.length() - 1);
178 if (property.endsWith(".dns") || property.endsWith(".dns1") ||
179 property.endsWith(".dns2") || property.endsWith(".dns3") ||
180 property.endsWith(".dns4")) {
181
182 // normalize the address
183
184 InetAddress ip = InetAddress.getByName(value);
185
186 if (ip == null) continue;
187
188 value = ip.getHostAddress();
189
190 if (value == null) continue;
191 if (value.length() == 0) continue;
192
193 server.add(value);
194 }
195 }
196 if (server.size() > 0) {
197 return server.toArray(new String[server.size()]);
198 }
199 } catch (IOException e) {
200 e.printStackTrace();
201 }
202 return null;
203 }
204
205 /**
206 * Try to retrieve the list of dns server by calling SystemProperties.
207 * @return Array of servers, or null on failure.
208 */
209 protected String[] findDNSByReflection() {
210 try {
211 Class<?> SystemProperties =
212 Class.forName("android.os.SystemProperties");
213 Method method = SystemProperties.getMethod("get",
214 new Class[] { String.class });
215
216 ArrayList<String> servers = new ArrayList<String>(5);
217
218 for (String propKey : new String[] {
219 "net.dns1", "net.dns2", "net.dns3", "net.dns4"}) {
220
221 String value = (String)method.invoke(null, propKey);
222
223 if (value == null) continue;
224 if (value.length() == 0) continue;
225 if (servers.contains(value)) continue;
226
227 InetAddress ip = InetAddress.getByName(value);
228
229 if (ip == null) continue;
230
231 value = ip.getHostAddress();
232
233 if (value == null) continue;
234 if (value.length() == 0) continue;
235 if (servers.contains(value)) continue;
236
237 servers.add(value);
238 }
239
240 if (servers.size() > 0) {
241 return servers.toArray(new String[servers.size()]);
242 }
243 } catch (Exception e) {
244 // we might trigger some problems this way
245 e.printStackTrace();
246 }
247 return null;
248 }
249
250}