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