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