Client.java

  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}