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;
 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}