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