blob: f2d84ebc9889f438a96ecaaa72770d040b54c518 [file] [log] [blame]
Isaac Levybc7dfb52011-06-06 15:34:01 -07001/*
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
Isaac Levya7bc1132011-07-06 13:54:48 -070017package android.net;
Isaac Levybc7dfb52011-06-06 15:34:01 -070018
Isaac Levybc7dfb52011-06-06 15:34:01 -070019import android.content.Context;
20import android.net.ConnectivityManager;
21import android.net.LinkProperties;
Isaac Levy3541ce02011-06-28 17:05:26 -070022import android.net.NetworkUtils;
Isaac Levybc7dfb52011-06-06 15:34:01 -070023import android.os.SystemClock;
Isaac Levy3541ce02011-06-28 17:05:26 -070024import android.provider.Settings;
Isaac Levybc7dfb52011-06-06 15:34:01 -070025import android.util.Slog;
26
27import java.net.DatagramPacket;
28import java.net.DatagramSocket;
29import java.net.InetAddress;
Isaac Levy3ee9d052011-06-27 22:37:12 -070030import java.net.NetworkInterface;
Isaac Levybc7dfb52011-06-06 15:34:01 -070031import 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>
Isaac Levy3541ce02011-06-28 17:05:26 -070043 * TODO : More general API. Socket does not bind to specified connection type
Isaac Levybc7dfb52011-06-06 15:34:01 -070044 * 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 */
Isaac Levy3ee9d052011-06-27 22:37:12 -070052 private static final int DNS_QUERY_BASE_SIZE = 32;
Isaac Levybc7dfb52011-06-06 15:34:01 -070053
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;
Isaac Levybc7dfb52011-06-06 15:34:01 -070061 private Context mContext;
Isaac Levyb1ef2922011-06-27 10:02:50 -070062 private int mConnectionType;
Isaac Levy3541ce02011-06-28 17:05:26 -070063 private InetAddress mDefaultDns;
Isaac Levybc7dfb52011-06-06 15:34:01 -070064
65 private String TAG;
66
Isaac Levyb1ef2922011-06-27 10:02:50 -070067 /**
Isaac Levy3541ce02011-06-28 17:05:26 -070068 * @param connectionType The connection type from {@link ConnectivityManager}
Isaac Levyb1ef2922011-06-27 10:02:50 -070069 */
70 public DnsPinger(String TAG, Context context, int connectionType) {
Isaac Levybc7dfb52011-06-06 15:34:01 -070071 mContext = context;
Isaac Levyb1ef2922011-06-27 10:02:50 -070072 mConnectionType = connectionType;
Isaac Levy3541ce02011-06-28 17:05:26 -070073 if (!ConnectivityManager.isNetworkTypeValid(connectionType)) {
74 Slog.e(TAG, "Invalid connectionType in constructor: " + connectionType);
75 }
Isaac Levybc7dfb52011-06-06 15:34:01 -070076 this.TAG = TAG;
Isaac Levy3541ce02011-06-28 17:05:26 -070077
78 mDefaultDns = getDefaultDns();
Isaac Levybc7dfb52011-06-06 15:34:01 -070079 }
80
81 /**
Isaac Levy3541ce02011-06-28 17:05:26 -070082 * @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.
Isaac Levybc7dfb52011-06-06 15:34:01 -070085 */
86 public InetAddress getDns() {
Isaac Levy3ee9d052011-06-27 22:37:12 -070087 LinkProperties curLinkProps = getCurrentLinkProperties();
Isaac Levy3541ce02011-06-28 17:05:26 -070088 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
Isaac Levy3ee9d052011-06-27 22:37:12 -0700102 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
Isaac Levy3541ce02011-06-28 17:05:26 -0700111 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 }
Isaac Levyb1ef2922011-06-27 10:02:50 -0700124 }
125
Isaac Levybc7dfb52011-06-06 15:34:01 -0700126 /**
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
Isaac Levy3ee9d052011-06-27 22:37:12 -0700137 // 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();
Isaac Levybc7dfb52011-06-06 15:34:01 -0700146
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
Isaac Levy3ee9d052011-06-27 22:37:12 -0700178 /**
179 * @return google.com DNS query packet
180 */
181 private static byte[] constructQuery() {
182 byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
Isaac Levybc7dfb52011-06-06 15:34:01 -0700183
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.
Isaac Levy3ee9d052011-06-27 22:37:12 -0700189 buf[2] = 0x01; // Recursion desired
Isaac Levybc7dfb52011-06-06 15:34:01 -0700190
Isaac Levy3ee9d052011-06-27 22:37:12 -0700191 // [4-5] bytes are for number of queries (QCOUNT)
192 buf[5] = 0x01;
Isaac Levybc7dfb52011-06-06 15:34:01 -0700193
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
Isaac Levy3ee9d052011-06-27 22:37:12 -0700199 // [16-22] for google
200 writeString(buf, 16, "google");
Isaac Levybc7dfb52011-06-06 15:34:01 -0700201
Isaac Levy3ee9d052011-06-27 22:37:12 -0700202 // [23-26] for com
203 writeString(buf, 23, "com");
Isaac Levybc7dfb52011-06-06 15:34:01 -0700204
Isaac Levy3ee9d052011-06-27 22:37:12 -0700205 // [27] is a null byte terminator byte for the url
Isaac Levybc7dfb52011-06-06 15:34:01 -0700206
Isaac Levy3ee9d052011-06-27 22:37:12 -0700207 // [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;
Isaac Levybc7dfb52011-06-06 15:34:01 -0700214 }
215
Isaac Levy3ee9d052011-06-27 22:37:12 -0700216 /**
217 * Writes the string's length and its contents to the buffer
218 */
Isaac Levybc7dfb52011-06-06 15:34:01 -0700219 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}