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