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