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 }
173 }
174 return null;
175 }
176
177 /**
178 * Retrieve a list of currently configured DNS servers.
179 * @return The server array.
180 */
181 public String[] findDNS() {
182 String[] result = findDNSByReflection();
183 if (result != null) {
184 LOGGER.fine("Got DNS servers via reflection: " + Arrays.toString(result));
185 return result;
186 }
187
188 result = findDNSByExec();
189 if (result != null) {
190 LOGGER.fine("Got DNS servers via exec: " + Arrays.toString(result));
191 return result;
192 }
193
194 // fallback for ipv4 and ipv6 connectivity
195 // see https://developers.google.com/speed/public-dns/docs/using
196 LOGGER.fine("No DNS found? Using fallback [8.8.8.8, [2001:4860:4860::8888]]");
197
198 return new String[]{"8.8.8.8", "[2001:4860:4860::8888]"};
199 }
200
201 /**
202 * Try to retrieve the list of dns server by executing getprop.
203 * @return Array of servers, or null on failure.
204 */
205 protected String[] findDNSByExec() {
206 try {
207 Process process = Runtime.getRuntime().exec("getprop");
208 InputStream inputStream = process.getInputStream();
209 LineNumberReader lnr = new LineNumberReader(
210 new InputStreamReader(inputStream));
211 String line = null;
212 HashSet<String> server = new HashSet<String>(6);
213 while ((line = lnr.readLine()) != null) {
214 int split = line.indexOf("]: [");
215 if (split == -1) {
216 continue;
217 }
218 String property = line.substring(1, split);
219 String value = line.substring(split + 4, line.length() - 1);
220 if (property.endsWith(".dns") || property.endsWith(".dns1") ||
221 property.endsWith(".dns2") || property.endsWith(".dns3") ||
222 property.endsWith(".dns4")) {
223
224 // normalize the address
225
226 InetAddress ip = InetAddress.getByName(value);
227
228 if (ip == null) continue;
229
230 value = ip.getHostAddress();
231
232 if (value == null) continue;
233 if (value.length() == 0) continue;
234
235 server.add(value);
236 }
237 }
238 if (server.size() > 0) {
239 return server.toArray(new String[server.size()]);
240 }
241 } catch (IOException e) {
242 LOGGER.log(Level.WARNING, "Exception in findDNSByExec", e);
243 }
244 return null;
245 }
246
247 /**
248 * Try to retrieve the list of dns server by calling SystemProperties.
249 * @return Array of servers, or null on failure.
250 */
251 protected String[] findDNSByReflection() {
252 try {
253 Class<?> SystemProperties =
254 Class.forName("android.os.SystemProperties");
255 Method method = SystemProperties.getMethod("get",
256 new Class[] { String.class });
257
258 ArrayList<String> servers = new ArrayList<String>(5);
259
260 for (String propKey : new String[] {
261 "net.dns1", "net.dns2", "net.dns3", "net.dns4"}) {
262
263 String value = (String)method.invoke(null, propKey);
264
265 if (value == null) continue;
266 if (value.length() == 0) continue;
267 if (servers.contains(value)) continue;
268
269 InetAddress ip = InetAddress.getByName(value);
270
271 if (ip == null) continue;
272
273 value = ip.getHostAddress();
274
275 if (value == null) continue;
276 if (value.length() == 0) continue;
277 if (servers.contains(value)) continue;
278
279 servers.add(value);
280 }
281
282 if (servers.size() > 0) {
283 return servers.toArray(new String[servers.size()]);
284 }
285 } catch (Exception e) {
286 // we might trigger some problems this way
287 LOGGER.log(Level.WARNING, "Exception in findDNSByReflection", e);
288 }
289 return null;
290 }
291
292}