blob: 7acf3f556595fab2666cfb3ddfdc6c6667fcaec4 [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;
Isaac Levyd2fe04b2011-07-22 08:48:26 -070020import android.os.Handler;
21import android.os.Looper;
22import android.os.Message;
Isaac Levybc7dfb52011-06-06 15:34:01 -070023import android.os.SystemClock;
Isaac Levy3541ce02011-06-28 17:05:26 -070024import android.provider.Settings;
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -070025import android.util.Log;
Isaac Levybc7dfb52011-06-06 15:34:01 -070026
Isaac Levyd2fe04b2011-07-22 08:48:26 -070027import com.android.internal.util.Protocol;
28
29import java.io.IOException;
Isaac Levybc7dfb52011-06-06 15:34:01 -070030import java.net.DatagramPacket;
31import java.net.DatagramSocket;
32import java.net.InetAddress;
Isaac Levy3ee9d052011-06-27 22:37:12 -070033import java.net.NetworkInterface;
Isaac Levybc7dfb52011-06-06 15:34:01 -070034import java.net.SocketTimeoutException;
Isaac Levyd2fe04b2011-07-22 08:48:26 -070035import java.util.ArrayList;
Isaac Levybc7dfb52011-06-06 15:34:01 -070036import java.util.Collection;
Isaac Levyd2fe04b2011-07-22 08:48:26 -070037import java.util.Iterator;
38import java.util.List;
Isaac Levybc7dfb52011-06-06 15:34:01 -070039import java.util.Random;
Isaac Levyd2fe04b2011-07-22 08:48:26 -070040import java.util.concurrent.atomic.AtomicInteger;
Isaac Levybc7dfb52011-06-06 15:34:01 -070041
42/**
43 * Performs a simple DNS "ping" by sending a "server status" query packet to the
44 * DNS server. As long as the server replies, we consider it a success.
45 * <p>
46 * We do not use a simple hostname lookup because that could be cached and the
47 * API may not differentiate between a time out and a failure lookup (which we
48 * really care about).
49 * <p>
Isaac Levybc7dfb52011-06-06 15:34:01 -070050 *
51 * @hide
52 */
Isaac Levyd2fe04b2011-07-22 08:48:26 -070053public final class DnsPinger extends Handler {
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -070054 private static final boolean DBG = false;
Isaac Levybc7dfb52011-06-06 15:34:01 -070055
Irfan Sheriff32f04e92011-09-23 14:48:26 -070056 private static final int RECEIVE_POLL_INTERVAL_MS = 200;
Isaac Levybc7dfb52011-06-06 15:34:01 -070057 private static final int DNS_PORT = 53;
58
Isaac Levyd2fe04b2011-07-22 08:48:26 -070059 /** Short socket timeout so we don't block one any 'receive' call */
60 private static final int SOCKET_TIMEOUT_MS = 1;
61
Isaac Levybc7dfb52011-06-06 15:34:01 -070062 /** Used to generate IDs */
Isaac Levyd2fe04b2011-07-22 08:48:26 -070063 private static final Random sRandom = new Random();
64 private static final AtomicInteger sCounter = new AtomicInteger();
Isaac Levybc7dfb52011-06-06 15:34:01 -070065
66 private ConnectivityManager mConnectivityManager = null;
Isaac Levyd2fe04b2011-07-22 08:48:26 -070067 private final Context mContext;
68 private final int mConnectionType;
69 private final Handler mTarget;
Isaac Levy79e43f62011-08-16 16:24:32 -070070 private final ArrayList<InetAddress> mDefaultDns;
Isaac Levybc7dfb52011-06-06 15:34:01 -070071 private String TAG;
72
Irfan Sheriff32f04e92011-09-23 14:48:26 -070073 //Invalidates old dns requests upon a cancel
74 private AtomicInteger mCurrentToken = new AtomicInteger();
75
Isaac Levyd2fe04b2011-07-22 08:48:26 -070076 private static final int BASE = Protocol.BASE_DNS_PINGER;
77
Isaac Levyb1ef2922011-06-27 10:02:50 -070078 /**
Isaac Levyd2fe04b2011-07-22 08:48:26 -070079 * Async response packet for dns pings.
80 * arg1 is the ID of the ping, also returned by {@link #pingDnsAsync(InetAddress, int, int)}
81 * arg2 is the delay, or is negative on error.
Isaac Levyb1ef2922011-06-27 10:02:50 -070082 */
Isaac Levyd2fe04b2011-07-22 08:48:26 -070083 public static final int DNS_PING_RESULT = BASE;
84 /** An error code for a {@link #DNS_PING_RESULT} packet */
85 public static final int TIMEOUT = -1;
86 /** An error code for a {@link #DNS_PING_RESULT} packet */
87 public static final int SOCKET_EXCEPTION = -2;
88
89 /**
90 * Send a new ping via a socket. arg1 is ID, arg2 is timeout, obj is InetAddress to ping
91 */
92 private static final int ACTION_PING_DNS = BASE + 1;
93 private static final int ACTION_LISTEN_FOR_RESPONSE = BASE + 2;
94 private static final int ACTION_CANCEL_ALL_PINGS = BASE + 3;
95
96 private List<ActivePing> mActivePings = new ArrayList<ActivePing>();
97 private int mEventCounter;
98
99 private class ActivePing {
100 DatagramSocket socket;
101 int internalId;
102 short packetId;
103 int timeout;
104 Integer result;
105 long start = SystemClock.elapsedRealtime();
106 }
107
Irfan Sheriff32f04e92011-09-23 14:48:26 -0700108 /* Message argument for ACTION_PING_DNS */
109 private class DnsArg {
110 InetAddress dns;
111 int seq;
112
113 DnsArg(InetAddress d, int s) {
114 dns = d;
115 seq = s;
116 }
117 }
118
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700119 public DnsPinger(Context context, String TAG, Looper looper,
120 Handler target, int connectionType) {
121 super(looper);
122 this.TAG = TAG;
Isaac Levybc7dfb52011-06-06 15:34:01 -0700123 mContext = context;
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700124 mTarget = target;
Isaac Levyb1ef2922011-06-27 10:02:50 -0700125 mConnectionType = connectionType;
Isaac Levy3541ce02011-06-28 17:05:26 -0700126 if (!ConnectivityManager.isNetworkTypeValid(connectionType)) {
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700127 throw new IllegalArgumentException("Invalid connectionType in constructor: "
128 + connectionType);
Isaac Levy3541ce02011-06-28 17:05:26 -0700129 }
Isaac Levy79e43f62011-08-16 16:24:32 -0700130 mDefaultDns = new ArrayList<InetAddress>();
131 mDefaultDns.add(getDefaultDns());
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700132 mEventCounter = 0;
133 }
134
135 @Override
136 public void handleMessage(Message msg) {
137 switch (msg.what) {
138 case ACTION_PING_DNS:
Irfan Sheriff32f04e92011-09-23 14:48:26 -0700139 DnsArg dnsArg = (DnsArg) msg.obj;
140 if (dnsArg.seq != mCurrentToken.get()) {
141 break;
142 }
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700143 try {
144 ActivePing newActivePing = new ActivePing();
Irfan Sheriff32f04e92011-09-23 14:48:26 -0700145 InetAddress dnsAddress = dnsArg.dns;
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700146 newActivePing.internalId = msg.arg1;
147 newActivePing.timeout = msg.arg2;
148 newActivePing.socket = new DatagramSocket();
149 // Set some socket properties
150 newActivePing.socket.setSoTimeout(SOCKET_TIMEOUT_MS);
151
152 // Try to bind but continue ping if bind fails
153 try {
154 newActivePing.socket.setNetworkInterface(NetworkInterface.getByName(
155 getCurrentLinkProperties().getInterfaceName()));
156 } catch (Exception e) {
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -0700157 loge("sendDnsPing::Error binding to socket " + e);
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700158 }
159
160 newActivePing.packetId = (short) sRandom.nextInt();
161 byte[] buf = mDnsQuery.clone();
162 buf[0] = (byte) (newActivePing.packetId >> 8);
163 buf[1] = (byte) newActivePing.packetId;
164
165 // Send the DNS query
166 DatagramPacket packet = new DatagramPacket(buf,
167 buf.length, dnsAddress, DNS_PORT);
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -0700168 if (DBG) {
169 log("Sending a ping " + newActivePing.internalId +
Isaac Levy26a8d712011-08-09 15:35:59 -0700170 " to " + dnsAddress.getHostAddress()
171 + " with packetId " + newActivePing.packetId + ".");
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700172 }
173
174 newActivePing.socket.send(packet);
175 mActivePings.add(newActivePing);
176 mEventCounter++;
177 sendMessageDelayed(obtainMessage(ACTION_LISTEN_FOR_RESPONSE, mEventCounter, 0),
178 RECEIVE_POLL_INTERVAL_MS);
179 } catch (IOException e) {
Isaac Levy26a8d712011-08-09 15:35:59 -0700180 sendResponse(msg.arg1, -9999, SOCKET_EXCEPTION);
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700181 }
182 break;
183 case ACTION_LISTEN_FOR_RESPONSE:
184 if (msg.arg1 != mEventCounter) {
185 break;
186 }
187 for (ActivePing curPing : mActivePings) {
188 try {
189 /** Each socket will block for {@link #SOCKET_TIMEOUT_MS} in receive() */
190 byte[] responseBuf = new byte[2];
191 DatagramPacket replyPacket = new DatagramPacket(responseBuf, 2);
192 curPing.socket.receive(replyPacket);
193 // Check that ID field matches (we're throwing out the rest of the packet)
194 if (responseBuf[0] == (byte) (curPing.packetId >> 8) &&
195 responseBuf[1] == (byte) curPing.packetId) {
196 curPing.result =
197 (int) (SystemClock.elapsedRealtime() - curPing.start);
198 } else {
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -0700199 if (DBG) {
200 log("response ID didn't match, ignoring packet");
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700201 }
202 }
203 } catch (SocketTimeoutException e) {
204 // A timeout here doesn't mean anything - squelsh this exception
205 } catch (Exception e) {
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -0700206 if (DBG) {
207 log("DnsPinger.pingDns got socket exception: " + e);
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700208 }
209 curPing.result = SOCKET_EXCEPTION;
210 }
211 }
212 Iterator<ActivePing> iter = mActivePings.iterator();
213 while (iter.hasNext()) {
214 ActivePing curPing = iter.next();
215 if (curPing.result != null) {
Isaac Levy26a8d712011-08-09 15:35:59 -0700216 sendResponse(curPing.internalId, curPing.packetId, curPing.result);
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700217 curPing.socket.close();
218 iter.remove();
219 } else if (SystemClock.elapsedRealtime() >
220 curPing.start + curPing.timeout) {
Isaac Levy26a8d712011-08-09 15:35:59 -0700221 sendResponse(curPing.internalId, curPing.packetId, TIMEOUT);
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700222 curPing.socket.close();
223 iter.remove();
224 }
225 }
226 if (!mActivePings.isEmpty()) {
227 sendMessageDelayed(obtainMessage(ACTION_LISTEN_FOR_RESPONSE, mEventCounter, 0),
228 RECEIVE_POLL_INTERVAL_MS);
229 }
230 break;
231 case ACTION_CANCEL_ALL_PINGS:
232 for (ActivePing activePing : mActivePings)
233 activePing.socket.close();
234 mActivePings.clear();
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700235 break;
236 }
Isaac Levybc7dfb52011-06-06 15:34:01 -0700237 }
238
239 /**
Isaac Levy79e43f62011-08-16 16:24:32 -0700240 * Returns a list of DNS addresses, coming from either the link properties of the
241 * specified connection or the default system DNS if the link properties has no dnses.
242 * @return a non-empty non-null list
Isaac Levybc7dfb52011-06-06 15:34:01 -0700243 */
Isaac Levy79e43f62011-08-16 16:24:32 -0700244 public List<InetAddress> getDnsList() {
Isaac Levy3ee9d052011-06-27 22:37:12 -0700245 LinkProperties curLinkProps = getCurrentLinkProperties();
Isaac Levy3541ce02011-06-28 17:05:26 -0700246 if (curLinkProps == null) {
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -0700247 loge("getCurLinkProperties:: LP for type" + mConnectionType + " is null!");
Isaac Levy3541ce02011-06-28 17:05:26 -0700248 return mDefaultDns;
249 }
250
Robert Greenwaltdf2b8782014-06-06 10:30:11 -0700251 Collection<InetAddress> dnses = curLinkProps.getDnsServers();
Isaac Levy3541ce02011-06-28 17:05:26 -0700252 if (dnses == null || dnses.size() == 0) {
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -0700253 loge("getDns::LinkProps has null dns - returning default");
Isaac Levy3541ce02011-06-28 17:05:26 -0700254 return mDefaultDns;
255 }
256
Isaac Levy79e43f62011-08-16 16:24:32 -0700257 return new ArrayList<InetAddress>(dnses);
Isaac Levy3541ce02011-06-28 17:05:26 -0700258 }
259
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700260 /**
261 * Send a ping. The response will come via a {@link #DNS_PING_RESULT} to the handler
262 * specified at creation.
263 * @param dns address of dns server to ping
264 * @param timeout timeout for ping
265 * @return an ID field, which will also be included in the {@link #DNS_PING_RESULT} message.
266 */
267 public int pingDnsAsync(InetAddress dns, int timeout, int delay) {
268 int id = sCounter.incrementAndGet();
Irfan Sheriff32f04e92011-09-23 14:48:26 -0700269 sendMessageDelayed(obtainMessage(ACTION_PING_DNS, id, timeout,
270 new DnsArg(dns, mCurrentToken.get())), delay);
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700271 return id;
272 }
273
274 public void cancelPings() {
Irfan Sheriff32f04e92011-09-23 14:48:26 -0700275 mCurrentToken.incrementAndGet();
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700276 obtainMessage(ACTION_CANCEL_ALL_PINGS).sendToTarget();
277 }
278
Isaac Levy26a8d712011-08-09 15:35:59 -0700279 private void sendResponse(int internalId, int externalId, int responseVal) {
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -0700280 if(DBG) {
281 log("Responding to packet " + internalId +
Isaac Levy26a8d712011-08-09 15:35:59 -0700282 " externalId " + externalId +
283 " and val " + responseVal);
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700284 }
285 mTarget.sendMessage(obtainMessage(DNS_PING_RESULT, internalId, responseVal));
286 }
287
Isaac Levy3ee9d052011-06-27 22:37:12 -0700288 private LinkProperties getCurrentLinkProperties() {
289 if (mConnectivityManager == null) {
290 mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
291 Context.CONNECTIVITY_SERVICE);
292 }
293
294 return mConnectivityManager.getLinkProperties(mConnectionType);
295 }
296
Isaac Levy3541ce02011-06-28 17:05:26 -0700297 private InetAddress getDefaultDns() {
Jeff Sharkey625239a2012-09-26 22:03:49 -0700298 String dns = Settings.Global.getString(mContext.getContentResolver(),
299 Settings.Global.DEFAULT_DNS_SERVER);
Isaac Levy3541ce02011-06-28 17:05:26 -0700300 if (dns == null || dns.length() == 0) {
301 dns = mContext.getResources().getString(
302 com.android.internal.R.string.config_default_dns_server);
303 }
304 try {
305 return NetworkUtils.numericToInetAddress(dns);
306 } catch (IllegalArgumentException e) {
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -0700307 loge("getDefaultDns::malformed default dns address");
Isaac Levy3541ce02011-06-28 17:05:26 -0700308 return null;
309 }
Isaac Levyb1ef2922011-06-27 10:02:50 -0700310 }
311
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700312 private static final byte[] mDnsQuery = new byte[] {
313 0, 0, // [0-1] is for ID (will set each time)
Isaac Levy26a8d712011-08-09 15:35:59 -0700314 1, 0, // [2-3] are flags. Set byte[2] = 1 for recursion desired (RD) on. Currently on.
Isaac Levyd2fe04b2011-07-22 08:48:26 -0700315 0, 1, // [4-5] bytes are for number of queries (QCOUNT)
316 0, 0, // [6-7] unused count field for dns response packets
317 0, 0, // [8-9] unused count field for dns response packets
318 0, 0, // [10-11] unused count field for dns response packets
319 3, 'w', 'w', 'w',
320 6, 'g', 'o', 'o', 'g', 'l', 'e',
321 3, 'c', 'o', 'm',
322 0, // null terminator of address (also called empty TLD)
323 0, 1, // QTYPE, set to 1 = A (host address)
324 0, 1 // QCLASS, set to 1 = IN (internet)
325 };
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -0700326
327 private void log(String s) {
328 Log.d(TAG, s);
329 }
330
331 private void loge(String s) {
332 Log.e(TAG, s);
333 }
Isaac Levybc7dfb52011-06-06 15:34:01 -0700334}