Home | History | Annotate | Download | only in net
      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 
     17 package android.net;
     18 
     19 import android.content.Context;
     20 import android.os.Handler;
     21 import android.os.Looper;
     22 import android.os.Message;
     23 import android.os.SystemClock;
     24 import android.provider.Settings;
     25 import android.util.Log;
     26 
     27 import com.android.internal.util.Protocol;
     28 
     29 import java.io.IOException;
     30 import java.net.DatagramPacket;
     31 import java.net.DatagramSocket;
     32 import java.net.InetAddress;
     33 import java.net.NetworkInterface;
     34 import java.net.SocketTimeoutException;
     35 import java.util.ArrayList;
     36 import java.util.Collection;
     37 import java.util.Iterator;
     38 import java.util.List;
     39 import java.util.Random;
     40 import java.util.concurrent.atomic.AtomicInteger;
     41 
     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>
     50  *
     51  * @hide
     52  */
     53 public final class DnsPinger extends Handler {
     54     private static final boolean DBG = false;
     55 
     56     private static final int RECEIVE_POLL_INTERVAL_MS = 200;
     57     private static final int DNS_PORT = 53;
     58 
     59     /** Short socket timeout so we don't block one any 'receive' call */
     60     private static final int SOCKET_TIMEOUT_MS = 1;
     61 
     62     /** Used to generate IDs */
     63     private static final Random sRandom = new Random();
     64     private static final AtomicInteger sCounter = new AtomicInteger();
     65 
     66     private ConnectivityManager mConnectivityManager = null;
     67     private final Context mContext;
     68     private final int mConnectionType;
     69     private final Handler mTarget;
     70     private final ArrayList<InetAddress> mDefaultDns;
     71     private String TAG;
     72 
     73     //Invalidates old dns requests upon a cancel
     74     private AtomicInteger mCurrentToken = new AtomicInteger();
     75 
     76     private static final int BASE = Protocol.BASE_DNS_PINGER;
     77 
     78     /**
     79      * 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.
     82      */
     83     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 
    108     /* 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 
    119     public DnsPinger(Context context, String TAG, Looper looper,
    120             Handler target, int connectionType) {
    121         super(looper);
    122         this.TAG = TAG;
    123         mContext = context;
    124         mTarget = target;
    125         mConnectionType = connectionType;
    126         if (!ConnectivityManager.isNetworkTypeValid(connectionType)) {
    127             throw new IllegalArgumentException("Invalid connectionType in constructor: "
    128                     + connectionType);
    129         }
    130         mDefaultDns = new ArrayList<InetAddress>();
    131         mDefaultDns.add(getDefaultDns());
    132         mEventCounter = 0;
    133     }
    134 
    135     @Override
    136     public void handleMessage(Message msg) {
    137         switch (msg.what) {
    138             case ACTION_PING_DNS:
    139                 DnsArg dnsArg = (DnsArg) msg.obj;
    140                 if (dnsArg.seq != mCurrentToken.get()) {
    141                     break;
    142                 }
    143                 try {
    144                     ActivePing newActivePing = new ActivePing();
    145                     InetAddress dnsAddress = dnsArg.dns;
    146                     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) {
    157                         loge("sendDnsPing::Error binding to socket " + e);
    158                     }
    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);
    168                     if (DBG) {
    169                         log("Sending a ping " + newActivePing.internalId +
    170                                 " to " + dnsAddress.getHostAddress()
    171                                 + " with packetId " + newActivePing.packetId + ".");
    172                     }
    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) {
    180                     sendResponse(msg.arg1, -9999, SOCKET_EXCEPTION);
    181                 }
    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 {
    199                             if (DBG) {
    200                                 log("response ID didn't match, ignoring packet");
    201                             }
    202                         }
    203                     } catch (SocketTimeoutException e) {
    204                         // A timeout here doesn't mean anything - squelsh this exception
    205                     } catch (Exception e) {
    206                         if (DBG) {
    207                             log("DnsPinger.pingDns got socket exception: " + e);
    208                         }
    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) {
    216                        sendResponse(curPing.internalId, curPing.packetId, curPing.result);
    217                        curPing.socket.close();
    218                        iter.remove();
    219                    } else if (SystemClock.elapsedRealtime() >
    220                                   curPing.start + curPing.timeout) {
    221                        sendResponse(curPing.internalId, curPing.packetId, TIMEOUT);
    222                        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();
    235                 break;
    236         }
    237     }
    238 
    239     /**
    240      * 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
    243      */
    244     public List<InetAddress> getDnsList() {
    245         LinkProperties curLinkProps = getCurrentLinkProperties();
    246         if (curLinkProps == null) {
    247             loge("getCurLinkProperties:: LP for type" + mConnectionType + " is null!");
    248             return mDefaultDns;
    249         }
    250 
    251         Collection<InetAddress> dnses = curLinkProps.getDnses();
    252         if (dnses == null || dnses.size() == 0) {
    253             loge("getDns::LinkProps has null dns - returning default");
    254             return mDefaultDns;
    255         }
    256 
    257         return new ArrayList<InetAddress>(dnses);
    258     }
    259 
    260     /**
    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();
    269         sendMessageDelayed(obtainMessage(ACTION_PING_DNS, id, timeout,
    270                 new DnsArg(dns, mCurrentToken.get())), delay);
    271         return id;
    272     }
    273 
    274     public void cancelPings() {
    275         mCurrentToken.incrementAndGet();
    276         obtainMessage(ACTION_CANCEL_ALL_PINGS).sendToTarget();
    277     }
    278 
    279     private void sendResponse(int internalId, int externalId, int responseVal) {
    280         if(DBG) {
    281             log("Responding to packet " + internalId +
    282                     " externalId " + externalId +
    283                     " and val " + responseVal);
    284         }
    285         mTarget.sendMessage(obtainMessage(DNS_PING_RESULT, internalId, responseVal));
    286     }
    287 
    288     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 
    297     private InetAddress getDefaultDns() {
    298         String dns = Settings.Global.getString(mContext.getContentResolver(),
    299                 Settings.Global.DEFAULT_DNS_SERVER);
    300         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) {
    307             loge("getDefaultDns::malformed default dns address");
    308             return null;
    309         }
    310     }
    311 
    312     private static final byte[] mDnsQuery = new byte[] {
    313         0, 0, // [0-1] is for ID (will set each time)
    314         1, 0, // [2-3] are flags.  Set byte[2] = 1 for recursion desired (RD) on.  Currently on.
    315         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     };
    326 
    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     }
    334 }
    335