/frameworks/base/core/java/android/net/DnsPinger.java
Java | 334 lines | 241 code | 35 blank | 58 comment | 34 complexity | d539aa5e563955f6a7c68b4badc1937e MD5 | raw file
- /*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package android.net;
- import android.content.Context;
- import android.os.Handler;
- import android.os.Looper;
- import android.os.Message;
- import android.os.SystemClock;
- import android.provider.Settings;
- import android.util.Log;
- import com.android.internal.util.Protocol;
- import java.io.IOException;
- import java.net.DatagramPacket;
- import java.net.DatagramSocket;
- import java.net.InetAddress;
- import java.net.NetworkInterface;
- import java.net.SocketTimeoutException;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Random;
- import java.util.concurrent.atomic.AtomicInteger;
- /**
- * Performs a simple DNS "ping" by sending a "server status" query packet to the
- * DNS server. As long as the server replies, we consider it a success.
- * <p>
- * We do not use a simple hostname lookup because that could be cached and the
- * API may not differentiate between a time out and a failure lookup (which we
- * really care about).
- * <p>
- *
- * @hide
- */
- public final class DnsPinger extends Handler {
- private static final boolean DBG = false;
- private static final int RECEIVE_POLL_INTERVAL_MS = 200;
- private static final int DNS_PORT = 53;
- /** Short socket timeout so we don't block one any 'receive' call */
- private static final int SOCKET_TIMEOUT_MS = 1;
- /** Used to generate IDs */
- private static final Random sRandom = new Random();
- private static final AtomicInteger sCounter = new AtomicInteger();
- private ConnectivityManager mConnectivityManager = null;
- private final Context mContext;
- private final int mConnectionType;
- private final Handler mTarget;
- private final ArrayList<InetAddress> mDefaultDns;
- private String TAG;
- //Invalidates old dns requests upon a cancel
- private AtomicInteger mCurrentToken = new AtomicInteger();
- private static final int BASE = Protocol.BASE_DNS_PINGER;
- /**
- * Async response packet for dns pings.
- * arg1 is the ID of the ping, also returned by {@link #pingDnsAsync(InetAddress, int, int)}
- * arg2 is the delay, or is negative on error.
- */
- public static final int DNS_PING_RESULT = BASE;
- /** An error code for a {@link #DNS_PING_RESULT} packet */
- public static final int TIMEOUT = -1;
- /** An error code for a {@link #DNS_PING_RESULT} packet */
- public static final int SOCKET_EXCEPTION = -2;
- /**
- * Send a new ping via a socket. arg1 is ID, arg2 is timeout, obj is InetAddress to ping
- */
- private static final int ACTION_PING_DNS = BASE + 1;
- private static final int ACTION_LISTEN_FOR_RESPONSE = BASE + 2;
- private static final int ACTION_CANCEL_ALL_PINGS = BASE + 3;
- private List<ActivePing> mActivePings = new ArrayList<ActivePing>();
- private int mEventCounter;
- private class ActivePing {
- DatagramSocket socket;
- int internalId;
- short packetId;
- int timeout;
- Integer result;
- long start = SystemClock.elapsedRealtime();
- }
- /* Message argument for ACTION_PING_DNS */
- private class DnsArg {
- InetAddress dns;
- int seq;
- DnsArg(InetAddress d, int s) {
- dns = d;
- seq = s;
- }
- }
- public DnsPinger(Context context, String TAG, Looper looper,
- Handler target, int connectionType) {
- super(looper);
- this.TAG = TAG;
- mContext = context;
- mTarget = target;
- mConnectionType = connectionType;
- if (!ConnectivityManager.isNetworkTypeValid(connectionType)) {
- throw new IllegalArgumentException("Invalid connectionType in constructor: "
- + connectionType);
- }
- mDefaultDns = new ArrayList<InetAddress>();
- mDefaultDns.add(getDefaultDns());
- mEventCounter = 0;
- }
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case ACTION_PING_DNS:
- DnsArg dnsArg = (DnsArg) msg.obj;
- if (dnsArg.seq != mCurrentToken.get()) {
- break;
- }
- try {
- ActivePing newActivePing = new ActivePing();
- InetAddress dnsAddress = dnsArg.dns;
- newActivePing.internalId = msg.arg1;
- newActivePing.timeout = msg.arg2;
- newActivePing.socket = new DatagramSocket();
- // Set some socket properties
- newActivePing.socket.setSoTimeout(SOCKET_TIMEOUT_MS);
- // Try to bind but continue ping if bind fails
- try {
- newActivePing.socket.setNetworkInterface(NetworkInterface.getByName(
- getCurrentLinkProperties().getInterfaceName()));
- } catch (Exception e) {
- loge("sendDnsPing::Error binding to socket " + e);
- }
- newActivePing.packetId = (short) sRandom.nextInt();
- byte[] buf = mDnsQuery.clone();
- buf[0] = (byte) (newActivePing.packetId >> 8);
- buf[1] = (byte) newActivePing.packetId;
- // Send the DNS query
- DatagramPacket packet = new DatagramPacket(buf,
- buf.length, dnsAddress, DNS_PORT);
- if (DBG) {
- log("Sending a ping " + newActivePing.internalId +
- " to " + dnsAddress.getHostAddress()
- + " with packetId " + newActivePing.packetId + ".");
- }
- newActivePing.socket.send(packet);
- mActivePings.add(newActivePing);
- mEventCounter++;
- sendMessageDelayed(obtainMessage(ACTION_LISTEN_FOR_RESPONSE, mEventCounter, 0),
- RECEIVE_POLL_INTERVAL_MS);
- } catch (IOException e) {
- sendResponse(msg.arg1, -9999, SOCKET_EXCEPTION);
- }
- break;
- case ACTION_LISTEN_FOR_RESPONSE:
- if (msg.arg1 != mEventCounter) {
- break;
- }
- for (ActivePing curPing : mActivePings) {
- try {
- /** Each socket will block for {@link #SOCKET_TIMEOUT_MS} in receive() */
- byte[] responseBuf = new byte[2];
- DatagramPacket replyPacket = new DatagramPacket(responseBuf, 2);
- curPing.socket.receive(replyPacket);
- // Check that ID field matches (we're throwing out the rest of the packet)
- if (responseBuf[0] == (byte) (curPing.packetId >> 8) &&
- responseBuf[1] == (byte) curPing.packetId) {
- curPing.result =
- (int) (SystemClock.elapsedRealtime() - curPing.start);
- } else {
- if (DBG) {
- log("response ID didn't match, ignoring packet");
- }
- }
- } catch (SocketTimeoutException e) {
- // A timeout here doesn't mean anything - squelsh this exception
- } catch (Exception e) {
- if (DBG) {
- log("DnsPinger.pingDns got socket exception: " + e);
- }
- curPing.result = SOCKET_EXCEPTION;
- }
- }
- Iterator<ActivePing> iter = mActivePings.iterator();
- while (iter.hasNext()) {
- ActivePing curPing = iter.next();
- if (curPing.result != null) {
- sendResponse(curPing.internalId, curPing.packetId, curPing.result);
- curPing.socket.close();
- iter.remove();
- } else if (SystemClock.elapsedRealtime() >
- curPing.start + curPing.timeout) {
- sendResponse(curPing.internalId, curPing.packetId, TIMEOUT);
- curPing.socket.close();
- iter.remove();
- }
- }
- if (!mActivePings.isEmpty()) {
- sendMessageDelayed(obtainMessage(ACTION_LISTEN_FOR_RESPONSE, mEventCounter, 0),
- RECEIVE_POLL_INTERVAL_MS);
- }
- break;
- case ACTION_CANCEL_ALL_PINGS:
- for (ActivePing activePing : mActivePings)
- activePing.socket.close();
- mActivePings.clear();
- break;
- }
- }
- /**
- * Returns a list of DNS addresses, coming from either the link properties of the
- * specified connection or the default system DNS if the link properties has no dnses.
- * @return a non-empty non-null list
- */
- public List<InetAddress> getDnsList() {
- LinkProperties curLinkProps = getCurrentLinkProperties();
- if (curLinkProps == null) {
- loge("getCurLinkProperties:: LP for type" + mConnectionType + " is null!");
- return mDefaultDns;
- }
- Collection<InetAddress> dnses = curLinkProps.getDnses();
- if (dnses == null || dnses.size() == 0) {
- loge("getDns::LinkProps has null dns - returning default");
- return mDefaultDns;
- }
- return new ArrayList<InetAddress>(dnses);
- }
- /**
- * Send a ping. The response will come via a {@link #DNS_PING_RESULT} to the handler
- * specified at creation.
- * @param dns address of dns server to ping
- * @param timeout timeout for ping
- * @return an ID field, which will also be included in the {@link #DNS_PING_RESULT} message.
- */
- public int pingDnsAsync(InetAddress dns, int timeout, int delay) {
- int id = sCounter.incrementAndGet();
- sendMessageDelayed(obtainMessage(ACTION_PING_DNS, id, timeout,
- new DnsArg(dns, mCurrentToken.get())), delay);
- return id;
- }
- public void cancelPings() {
- mCurrentToken.incrementAndGet();
- obtainMessage(ACTION_CANCEL_ALL_PINGS).sendToTarget();
- }
- private void sendResponse(int internalId, int externalId, int responseVal) {
- if(DBG) {
- log("Responding to packet " + internalId +
- " externalId " + externalId +
- " and val " + responseVal);
- }
- mTarget.sendMessage(obtainMessage(DNS_PING_RESULT, internalId, responseVal));
- }
- private LinkProperties getCurrentLinkProperties() {
- if (mConnectivityManager == null) {
- mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
- Context.CONNECTIVITY_SERVICE);
- }
- return mConnectivityManager.getLinkProperties(mConnectionType);
- }
- private InetAddress getDefaultDns() {
- String dns = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.DEFAULT_DNS_SERVER);
- if (dns == null || dns.length() == 0) {
- dns = mContext.getResources().getString(
- com.android.internal.R.string.config_default_dns_server);
- }
- try {
- return NetworkUtils.numericToInetAddress(dns);
- } catch (IllegalArgumentException e) {
- loge("getDefaultDns::malformed default dns address");
- return null;
- }
- }
- private static final byte[] mDnsQuery = new byte[] {
- 0, 0, // [0-1] is for ID (will set each time)
- 1, 0, // [2-3] are flags. Set byte[2] = 1 for recursion desired (RD) on. Currently on.
- 0, 1, // [4-5] bytes are for number of queries (QCOUNT)
- 0, 0, // [6-7] unused count field for dns response packets
- 0, 0, // [8-9] unused count field for dns response packets
- 0, 0, // [10-11] unused count field for dns response packets
- 3, 'w', 'w', 'w',
- 6, 'g', 'o', 'o', 'g', 'l', 'e',
- 3, 'c', 'o', 'm',
- 0, // null terminator of address (also called empty TLD)
- 0, 1, // QTYPE, set to 1 = A (host address)
- 0, 1 // QCLASS, set to 1 = IN (internet)
- };
- private void log(String s) {
- Log.d(TAG, s);
- }
- private void loge(String s) {
- Log.e(TAG, s);
- }
- }