DnsPinger.java revision a7bc1135c270fd4a84ab7ad45b7194e9b580300e
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.net;
18
19import android.content.Context;
20import android.net.ConnectivityManager;
21import android.net.LinkProperties;
22import android.net.NetworkUtils;
23import android.os.SystemClock;
24import android.provider.Settings;
25import android.util.Slog;
26
27import java.net.DatagramPacket;
28import java.net.DatagramSocket;
29import java.net.InetAddress;
30import java.net.NetworkInterface;
31import java.net.SocketTimeoutException;
32import java.util.Collection;
33import java.util.Random;
34
35/**
36 * Performs a simple DNS "ping" by sending a "server status" query packet to the
37 * DNS server. As long as the server replies, we consider it a success.
38 * <p>
39 * We do not use a simple hostname lookup because that could be cached and the
40 * API may not differentiate between a time out and a failure lookup (which we
41 * really care about).
42 * <p>
43 * TODO : More general API. Socket does not bind to specified connection type
44 * TODO : Choice of DNS query location - current looks up www.android.com
45 *
46 * @hide
47 */
48public final class DnsPinger {
49    private static final boolean V = true;
50
51    /** Number of bytes for the query */
52    private static final int DNS_QUERY_BASE_SIZE = 32;
53
54    /** The DNS port */
55    private static final int DNS_PORT = 53;
56
57    /** Used to generate IDs */
58    private static Random sRandom = new Random();
59
60    private ConnectivityManager mConnectivityManager = null;
61    private Context mContext;
62    private int mConnectionType;
63    private InetAddress mDefaultDns;
64
65    private String TAG;
66
67    /**
68     * @param connectionType The connection type from {@link ConnectivityManager}
69     */
70    public DnsPinger(String TAG, Context context, int connectionType) {
71        mContext = context;
72        mConnectionType = connectionType;
73        if (!ConnectivityManager.isNetworkTypeValid(connectionType)) {
74            Slog.e(TAG, "Invalid connectionType in constructor: " + connectionType);
75        }
76        this.TAG = TAG;
77
78        mDefaultDns = getDefaultDns();
79    }
80
81    /**
82     * @return The first DNS in the link properties of the specified connection
83     *         type or the default system DNS if the link properties has null
84     *         dns set. Should not be null.
85     */
86    public InetAddress getDns() {
87        LinkProperties curLinkProps = getCurrentLinkProperties();
88        if (curLinkProps == null) {
89            Slog.e(TAG, "getCurLinkProperties:: LP for type" + mConnectionType + " is null!");
90            return mDefaultDns;
91        }
92
93        Collection<InetAddress> dnses = curLinkProps.getDnses();
94        if (dnses == null || dnses.size() == 0) {
95            Slog.v(TAG, "getDns::LinkProps has null dns - returning default");
96            return mDefaultDns;
97        }
98
99        return dnses.iterator().next();
100    }
101
102    private LinkProperties getCurrentLinkProperties() {
103        if (mConnectivityManager == null) {
104            mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
105                    Context.CONNECTIVITY_SERVICE);
106        }
107
108        return mConnectivityManager.getLinkProperties(mConnectionType);
109    }
110
111    private InetAddress getDefaultDns() {
112        String dns = Settings.Secure.getString(mContext.getContentResolver(),
113                Settings.Secure.DEFAULT_DNS_SERVER);
114        if (dns == null || dns.length() == 0) {
115            dns = mContext.getResources().getString(
116                    com.android.internal.R.string.config_default_dns_server);
117        }
118        try {
119            return NetworkUtils.numericToInetAddress(dns);
120        } catch (IllegalArgumentException e) {
121            Slog.w(TAG, "getDefaultDns::malformed default dns address");
122            return null;
123        }
124    }
125
126    /**
127     * @return time to response. Negative value on error.
128     */
129    public long pingDns(InetAddress dnsAddress, int timeout) {
130        DatagramSocket socket = null;
131        try {
132            socket = new DatagramSocket();
133
134            // Set some socket properties
135            socket.setSoTimeout(timeout);
136
137            // Try to bind but continue ping if bind fails
138            try {
139                socket.setNetworkInterface(NetworkInterface.getByName(
140                        getCurrentLinkProperties().getInterfaceName()));
141            } catch (Exception e) {
142                Slog.d(TAG,"pingDns::Error binding to socket", e);
143            }
144
145            byte[] buf = constructQuery();
146
147            // Send the DNS query
148
149            DatagramPacket packet = new DatagramPacket(buf,
150                    buf.length, dnsAddress, DNS_PORT);
151            long start = SystemClock.elapsedRealtime();
152            socket.send(packet);
153
154            // Wait for reply (blocks for the above timeout)
155            DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
156            socket.receive(replyPacket);
157
158            // If a timeout occurred, an exception would have been thrown. We
159            // got a reply!
160            return SystemClock.elapsedRealtime() - start;
161
162        } catch (SocketTimeoutException e) {
163            // Squelch this exception.
164            return -1;
165        } catch (Exception e) {
166            if (V) {
167                Slog.v(TAG, "DnsPinger.pingDns got socket exception: ", e);
168            }
169            return -2;
170        } finally {
171            if (socket != null) {
172                socket.close();
173            }
174        }
175
176    }
177
178    /**
179     * @return google.com DNS query packet
180     */
181    private static byte[] constructQuery() {
182        byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
183
184        // [0-1] bytes are an ID, generate random ID for this query
185        buf[0] = (byte) sRandom.nextInt(256);
186        buf[1] = (byte) sRandom.nextInt(256);
187
188        // [2-3] bytes are for flags.
189        buf[2] = 0x01; // Recursion desired
190
191        // [4-5] bytes are for number of queries (QCOUNT)
192        buf[5] = 0x01;
193
194        // [6-7] [8-9] [10-11] are all counts of other fields we don't use
195
196        // [12-15] for www
197        writeString(buf, 12, "www");
198
199        // [16-22] for google
200        writeString(buf, 16, "google");
201
202        // [23-26] for com
203        writeString(buf, 23, "com");
204
205        // [27] is a null byte terminator byte for the url
206
207        // [28-29] bytes are for QTYPE, set to 1 = A (host address)
208        buf[29] = 0x01;
209
210        // [30-31] bytes are for QCLASS, set to 1 = IN (internet)
211        buf[31] = 0x01;
212
213        return buf;
214    }
215
216    /**
217     * Writes the string's length and its contents to the buffer
218     */
219    private static void writeString(byte[] buf, int startPos, String string) {
220        int pos = startPos;
221
222        // Write the length first
223        buf[pos++] = (byte) string.length();
224        for (int i = 0; i < string.length(); i++) {
225            buf[pos++] = (byte) string.charAt(i);
226        }
227    }
228}
229